1、算法思想

1.1、理解算法过程
  • 我们在写算法的时候要先理解我们的对象和之间的关系,我这里举例供求信息用户设置标签,两者关系是,系统会根据用户设置的标签来匹配与其相似度较高的,同时用户发布的供求信息的标签也会影响系统推荐的供求信息,这里还需要涉及到权重问题

(1)我们应该采用什么计算方式来计算,我这里采用简单 交集 / 并集 计算相似度的计算方法。

(2)还需要考虑以下三大方面的影响因素 :

  • 个人标签设置(A:时间衰减度,B:相似度计算)
  • 发布供求标签(C:时间衰减度,D:相似度计算,E:商机类型占比)
  • 其他因素(F:供求发布时间衰减度,G:移除自己发布的供求,H:企业认证/个人认证/VIP特权/权重等)

(3)以下是相关计算:

  • A/C/F:时间衰减度 = 更新时间戳 / 当前时间 * β(β为一个设置的稳定参数,根据数据分析去设置)
  • B/D:相似度计算如下:另外还有其他的 相似度计算方法

假设 A = 某条供求与用户标签的相似度
假设 B = 某条供求与用户发布供求的标签相似度
假设 C = 某条供求标签与用户标签交集总数
假设 D = 某条供求标签与用户标签并集总数
假设 X = 某条供求标签与用户发布供求的标签交集总数
假设 Y = 某条供求标签与用户发布供求的标签并集总数

公式1:某条供求的相似度 = A * 占比 + B * 占比
公式2:A = C / D * 出现概率(默认是1,因为用户无重复标签)
公式3:B = X / Y * 出现概率

  • E:用户发布商机类型占比是根据自己业务需求去加的,不是必须项。

假设A = 用户发布的出售类型商机数量
假设B = 用户发布的求购类型商机数量

用户发布出售类型商机占比 C = A / ( A + B )
用户发布求购类型商机占比 D = B / ( A + B )
那么用户需求则正好相反,推荐的出售商机占比为 D,推荐的求购商机占比为 C
另外如果用户未发不过商机和求购则按照 1:1
如果用户发布的全是求购则推给他的 出售:求购 = 9 : 1(这里的9:1自行设置)

  • G:将个人发布的供求 排除 在推荐列表中,供求数据采用 缓存存储
  • H:这里的其他参数也是需要平衡后才能加入进行相似度计算的。

(4)采用 自定义分页 筛选后再进行 数据库查询

1.2、实操分步解析

1、将数据库供求列表存储到 Redis 中,可以用 hash 存储,如下图:

  • 保存的时候注意这里的域key是 对应供求的ID ,值则是 供求的数据 ,里面的field最好是用到的才存进去,不然数据量大的话取出来的速度也会降低,影响首页内容输出速率。

我们要注意的是每次 发布一条供求 或者 审核通过 时候将该条 保存到redis 中,这样就不用全部导入了

2、需要分 游客用户 两种登录情况的推荐。正常情况下,游客就按照数据库的排序就行了。

3、需要将 自己发布的供求 移除 推荐列表

4、封装统一的 计算相似度的方法,这样便于用,同时要考虑 用户未设置标签或未发布一条供求的情况

5、封装对应的 分页方法,我在下面也会提供我封装的方法。

2、代码实现

2.1、获取推荐列表的方法(我是封装成服务类方法)
/*** 推荐算法返回商机* @param int $userId 用户ID* @param int $page 页码* @param int $pagesize 每页条数* @return bool* @throws \think\Exception* @throws \think\db\exception\DataNotFoundException* @throws \think\db\exception\ModelNotFoundException* @throws \think\exception\DbException*/
public function recommendBusiness($userId, $page = 1, $pagesize = 10)
{//从缓存中取出所有的文章信息$redis = RedisService::connect();$redisKey = RedisService::SU_CACHE_BUSINESS_TAGS;$data = $redis->hgetall($redisKey);//注意保存的数组中需要保存原始的key,因为该key是供求ID$businessArr = []; //存放供求列表内容$labelArr = []; //存放供求列表标签foreach ($data as $key => $val) {$val = json_decode($val, 1);$businessArr[$key] = $val;$labelArr[$key] = explode('-', $val['label_ids']);}//组建查所有的商机的sql$field = 'b.id,substring_index(b.images,\',\',1) as image,b.purpose,b.type,b.desc,b.price,b.number,u.vip_id,u.company,u.avatar,u.nickname,u.credit_score,b.city,b.label_name,b.color,b.update_time,vl.icon,b.create_time';if ($userId) {//取出当前用户的行业标签$user = (new UserModel)->alias('u')->join('user_industry ui', 'u.id = ui.user_id', 'LEFT')->where('u.id', $userId)->field(['u.id', 'ui.p_name', 'ui.s_name', 'ui.update_time'])->find()->toArray();//查询当前用户发布的供求$business = (new BusinessModel)->where('status', 1)->where('user_id', $userId)->field(['id', 'type', 'label_ids', 'update_time'])->select();if (!empty($business) && $user['p_name'] != null) {$userBusinessLabel = []; //存放用户发布商机标签的数组(有重复数据,需要计算出现概率)$sellCount = 0; //发布的出售数量$buyCount = 0; //发布的求购数量foreach ($business as $k => $v) {//根据发布时间计算衰减度$timeRate = 1;$busTimeRate = strtotime($v['update_time']) / time() * $timeRate;//商机类型:0=求购,1=出售if ($v['type'] == 0) $buyCount += $busTimeRate;if ($v['type'] == 1) $sellCount += $busTimeRate;//把当前用户的供求给移除推荐列表$bId = $v['id'];unset($businessArr[$bId]);//合并数组,存放用户发布商机标签的数组$val = explode('-', $v['label_ids']);$userBusinessLabel = array_merge($userBusinessLabel, $val);}//----------------------------查出用户最近发布的供求的品类ID进行计算相似度----------------------------//用于用户发布供求标签匹配的相似度$similarBusinessArr = $this->calculateSimilar($labelArr, $userBusinessLabel);//--------------------------------------------------------------------------------------------------//------------------------------以下是求行业标签与发布的品类标签的相似度------------------------------//拼接用户的行业标签名称去匹配品类ID数组$sonNames = explode(',', $user['s_name']);$pNames = explode(',', $user['p_name']);$nameArr = array_merge($sonNames, $pNames);$userLabel = (new TexturetypeModel)->whereIn('name', $nameArr)->column('id');//用于用户标签匹配的相似度$similarIndustryArr = $this->calculateSimilar($labelArr, $userLabel);//--------------------------------------------------------------------------------------------------//权重计算$weigh = []; //用于存放推荐算法之后的权重数组foreach ($businessArr as $key => $val) {//影响因素1:计算求购需求和出售需求占比if ($sellCount != 0 && $buyCount != 0) {//如果都占有则计算占比$allNeedRate = bcadd($sellCount, $buyCount, 4);$buyNeedRate = bcdiv($sellCount, $allNeedRate, 4);$sellNeedRate = bcdiv($buyCount, $allNeedRate, 4);} elseif ($buyCount == 0 && $sellCount == 0) {//如果都为0时候则需要$sellNeedRate = 0.5;$buyNeedRate = 0.5;} elseif ($sellCount == 0) {//如果未发布过出售,只发布求购则推10%的求购单,90%的出售单$buyNeedRate = 0.1;$sellNeedRate = 0.9;} else {//如果未发布过求购,只发布出售则推90%的求购单,10%的出售单$buyNeedRate = 0.9;$sellNeedRate = 0.1;}//影响因素2:标签设置时间进行兴趣衰减$timeRate2 = 1; //兴趣衰弱占比$labelTimeRate = strtotime($user['update_time']) / time() * $timeRate2;//影响因素3:商机发布的时间衰减度$timeRate3 = 1; //兴趣衰弱占比$busTimeRate = strtotime($val['update_time']) / time() * $timeRate3;//商机类型:0=求购,1=出售//最终权重 = (标签相似度 * 标签设置兴趣衰减度 * 占比) + (发布供求相似度 * 发布供求需求占比 * 占比)if ($val['type'] == 0) $similarBusArr = $similarBusinessArr[$key] * $buyNeedRate;if ($val['type'] == 1) $similarBusArr = $similarBusinessArr[$key] * $sellNeedRate;$weigh[$key] = ($similarIndustryArr[$key] * $labelTimeRate * 0.35 + $similarBusArr * 0.65) * $busTimeRate;}arsort($weigh); //按相似度,最相似的排最前面arrayToPage($weigh, $page, $pagesize, 0, true); //进行自定义分页处理$businessIds = array_keys($weigh); //取出所有的键值$exp = new Expression('field(b.id,' . implode(',', $businessIds) . ')'); //用于排序$list = (new BusinessModel)->alias('b')->join('user u', 'b.user_id = u.id', 'left')->join('sulink_vip_level vl', 'vl.id = u.vip_id', 'left')->where('b.status', 1);if (!empty($businessIds)) $list->whereIn('b.id', $businessIds)->order($exp);$list = $list->field($field)->select();} else {//当用户未发布商机和供求时候游览$list = (new BusinessModel)->alias('b')->join('user u', 'b.user_id = u.id', 'left')->join('sulink_vip_level vl', 'vl.id = u.vip_id', 'left')->where('b.status', 1)->field($field)->order('vl.weigh', 'DESC')->order('b.weigh', 'DESC')->order('u.is_enterprise_certification', 'DESC')->order('u.is_certification', 'DESC')->order('b.update_time', 'DESC')->page($page, $pagesize)->select();}} else {//游客游览时候$list = (new BusinessModel)->alias('b')->join('user u', 'b.user_id = u.id', 'left')->join('sulink_vip_level vl', 'vl.id = u.vip_id', 'left')->where('b.status', 1)->field($field)->order('vl.weigh', 'DESC')->order('b.weigh', 'DESC')->order('u.is_enterprise_certification', 'DESC')->order('u.is_certification', 'DESC')->order('b.update_time', 'DESC')->page($page, $pagesize)->select();}$list = collection($list)->toArray();//分隔符foreach ($list as $index => &$item) {$city = explode('/', $item['city']);$city = mb_substr($city[0], 0, 2, 'UTF-8');$item['label_name'] = explode(' - ', $item['label_name']);array_unshift($item['label_name'], $city);if ($item['color'] ?? null) {$item['label_name'][] = $item['color'];}//多少时间前$list[$index]['update_time'] = timeToBefore(strtotime($list[$index]['update_time']));//删除不需要的字段unset($list[$index]['city'], $list[$index]['color']);}return $list;
}
2.2、计算相似度的代码
/*** 用于计算相似度(传入的必须是一位数组,value是对应的标签ID)* @param array $data 大数组(大数组的key是供求ID)* @param array $inArr 小数组* @return array*/
private function calculateSimilar($data, $inArr)
{//计算$inArr中标签出现概率$total = count($inArr);$countArr = $total != 0 ? array_count_values($inArr) : 0; //转换成ID作为key,出现次数作为value 的一维数组(主要用于计算用户发布过的商机)$probability = $total != 0 ? 1 / $total : 1;//默认概率$arr = []; //相似度数组foreach ($data as $key => $val) {//公式:相似度 = 交集/并集 * 概率$intersect = array_intersect($val, $inArr); //计算交集$union = array_unique(array_merge($val, $inArr)); //计算并集if ($total != 0) {if ($countArr) {//如果有则计算概率,其中一二级都会foreach ($countArr as $k => $v)if ($k == $val[0] || $k == $val[1]) $probability = $v / $total;} else {$probability = 1 / $total;}}$arr[$key] = (float)(count($intersect) / count($union) * $probability);}return $arr;
}
2.3、封装的自定义分页
/*** 将多维数组继续分页,自定义分页效果* @param array &$array 数组* @param int $page 当前页数* @param int $limit 每页页数* @param int $order 0-不变 1-反序* @param bool $preserveKey true - 保留键名  false - 默认。重置键名*/
function arrayToPage(Array &$array, int $page = 1, int $limit = 20, int $order = 0,bool $preserveKey = false)
{$start = ($page - 1) * $limit; //计算每次分页的开始位置//反序if ($order == 1) $array = array_reverse($array);$array = array_slice($array, $start, $limit,$preserveKey);
}

3、注意要点,解释上面代码中主要部分

3.1、其中主要的计算部分如下

//----------------------------查出用户最近发布的供求的品类ID进行计算相似度----------------------------
//用于用户发布供求标签匹配的相似度
$similarBusinessArr = $this->calculateSimilar($labelArr, $userBusinessLabel);
//--------------------------------------------------------------------------------------------------//------------------------------以下是求行业标签与发布的品类标签的相似度------------------------------
//拼接用户的行业标签名称去匹配品类ID数组
$sonNames = explode(',', $user['s_name']);
$pNames = explode(',', $user['p_name']);
$nameArr = array_merge($sonNames, $pNames);
$userLabel = (new TexturetypeModel)->whereIn('name', $nameArr)->column('id');
//用于用户标签匹配的相似度
$similarIndustryArr = $this->calculateSimilar($labelArr, $userLabel);
//--------------------------------------------------------------------------------------------------
  • 函数中传的参数必须是一维数组,其中

  • $userBusinessLabel 当前用户发布的供求标签一维数组(有重复数据,需要计算出现概率)

  • $userLabel 用户自定义的标签,无重复数据

3.2、最后总的计算并排序
//权重计算
$weigh = []; //用于存放推荐算法之后的权重数组
foreach ($businessArr as $key => $val) {//计算求购需求和出售需求占比if ($buyCount == 0 && $sellCount == 0) {$sellNeedRate = 0.5;$buyNeedRate = 0.5;} else {$buyNeedRate = $sellCount / ($sellCount + $buyCount);$sellNeedRate = $buyCount / ($sellCount + $buyCount);//如果是比例是0和1的话需要对应加10%和90%的基数if ($buyNeedRate == 0) $buyNeedRate = 0.1;if ($buyNeedRate == 1) $buyNeedRate = 0.9;if ($sellNeedRate == 0) $sellNeedRate = 0.1;if ($sellNeedRate == 1) $sellNeedRate = 0.9;}$similar = $similarIndustryArr[$key] * 0.25 + $similarBusinessArr[$key] * 0.75;//商机类型:0=求购,1=出售if ($val['type'] == 0) $weigh[$key] = $similar * $buyNeedRate;if ($val['type'] == 1) $weigh[$key] = $similar * $sellNeedRate;
}arsort($weigh); //按相似度,最相似的排最前面
  • 其中我根据出售和求购的两种类型进行占比计算,我们还可以另外加其他的占比情况。
  • 我们在计算过程中需要考虑分母不能为0的情况。
  • 除此之外我们还可以用权重算法进行计算,需要根据业务和数据测试之后确定适合自己的算法。
  • 我们在计算过程中需要考虑到极限的情况,我们需要在这种情况下加上基数稳定推荐算法。

PS : 上面是我初次接触使用简单的推荐算法,如果有什么不对的地方请指教,我还会一直完善。由于数据的不足,还没有进入测试阶段,所以我还是需要去不断改进。

TP5 实现基于标签简单的推荐算法相关推荐

  1. 基于矩阵分解的推荐算法,简单入门

    摘自:http://www.cnblogs.com/kobedeshow/p/3651833.html 本文将要讨论基于矩阵分解的推荐算法,这一类型的算法通常会有很高的预测精度,也活跃于各大推荐系统竞 ...

  2. 基于内容推荐算法html,基于内容的互联网推荐算法

    摘 要 本文介B了网络推荐的算法思想.帮助读者了解这个研究领域.在介绍了推荐系统的概念和定义之后,重点介绍了基于内容的互联网推荐算法. 关键词 推荐系统 推荐算法 互联网 中图分类号:TP391.3 ...

  3. 基于内容的个性化推荐算法

    一.什么是推荐算法 随着移动互联网的高速发展与智能手机的普及,海量的有用信息虽然为人们提供了更多的价值,然而信息的泛滥也意味着为了寻找合适的信息必须付出更多的时间成本.事实上,有时候仅仅是浏览和简单的 ...

  4. 推荐系统 --- 推荐算法 --- 基于用户行为的推荐算法 - 协同过滤算法

    概述 历史 1992年,Goldberg.Nicols.Oki及Terry提出 基本思想 爱好相似的用户喜欢的东西可能也会喜欢 优点 共享朋友的经验,提高推荐的准确度 根据爱好相似的用户喜欢的视频进行 ...

  5. (附源码)ssm+mysql+基于ssm协同过滤推荐算法的电影院购票系统 毕业设计131124

    基于ssm协同过滤推荐算法的电影院购票系统 摘 要 随着信息技术的飞速发展以及Internet的迅速普及,以Web2.0为代表的互联网技术使得网络数据呈爆炸式增长,越来越多的信息和服务充斥着网络.人们 ...

  6. 基于内容的推荐算法的php实现,基于内容的个性化推荐算法

    摘要:个性化推荐算法有许多类别,主要包括基于内容的推荐.协同过滤.SVD.基于知识的推荐以及混合推荐算法.本文介绍基于内容的推荐算法(Content-basedRecommendation).基于内容 ...

  7. 基于机器学习的内容推荐算法及其心理学、社会学影响闲谈

    基于机器学习的内容推荐算法目前在各类内容类APP中使用的非常普遍.在购物.时尚.新闻咨询.学习等领域,根据用户的喜好,进行较为精准的用户画像与内容推荐.此类算法不但可以较为准确的分析用户的特征,如年龄 ...

  8. 基于机器学习的个性化推荐算法的研究

    基于 机器学习 的个性化推荐算法的研究 摘要: 如今互联网发展 十分迅速,每天产生的数据量一直在增加,传统的搜索引擎已经不能够适用当前的需求,推荐系统已经成为互联网时代的新宠儿.它已经发展成为一门跨学 ...

  9. 推荐算法-基于协同过滤的推荐算法

    推荐算法-基于协同过滤的推荐算法 在如今信息量呈爆炸式增长的时代,谷歌百度等搜索引擎为人们查找信息提供了便利,帮助用户快速查找有价值的信息.然而此类查询方式是大众化的,无法根据个人兴趣为用户展示相关的 ...

最新文章

  1. 从 AlphaGo 到具有人类智慧的 AI 究竟有多远?Keras之父François Chollet告诉你
  2. 极简易版专家聊天程序--JAVA练手
  3. oracle sql字符拆分字符串函数,oracle-是否有在PL / SQL中拆分字符串的功能?
  4. do not lie on the bed to watch pc or phones
  5. 在gridview里查找模板里的button控件
  6. .NET Core 跨平台物联网框架 ServerSuperIO.Core,一套设备驱动通吃嵌入式、上位机、云服务...
  7. ASP.NETCore的Kestrel服务器
  8. 周末狂欢赛3(跳格子,英雄联盟,排序问题)
  9. Windows下Yarn安装与使用
  10. ASP.NET MVC 4应用程序文件夹
  11. C#/VB.NET与西门子PLC进行ModbusTcp通信
  12. Exchange的AutoDiscover服务
  13. AI+大数据顶级技术盛会倒计时10天,6.6折票限时特惠!
  14. python 字典查询比列表快_为什么python字典要比列表快以及哈希查找解释。
  15. 北京市市级行政区域数据
  16. 如何比较两条回归直线
  17. 本科英语计算机,计算机本科生英语简历范文
  18. js中Error对象
  19. 基于ARM裸机的知识点总结(9)------- S5PV210的定时器、看门狗和RTC
  20. js获取当前日期前12月后12月日期

热门文章

  1. Node的垃圾回收机制与内存溢出捕获(上)
  2. Elasticsearch 2.20入门篇:基本操作
  3. Qt 2D绘图之二:抗锯齿渲染和坐标系统
  4. DX使用随记--GroupControl
  5. 在 Linux 中使用超级用户权限
  6. 织梦gbk转utf8(数据库篇)
  7. HihoCoder 1513 : 小Hi的烦恼
  8. TCP/UDP编程中的问题汇总
  9. XCode4 实践HelloWorld
  10. Codeforces Round #555 (Div. 3) AB