(四)基于商品属性的相似商品推荐算法——推荐与评分高的商品属性相似的商品
系列随笔:
(二)基于商品属性的相似商品推荐算法——Flink SQL实时计算实现商品的隐式评分
(三)基于商品属性的相似商品推荐算法——批量处理商品属性,得到属性前缀及完整属性字符串
(四)基于商品属性的相似商品推荐算法——推荐与评分高的商品属性相似的商品
2020.04.15 补充:协同过滤推荐算法.pptx
提取码:4tds
推荐与评分高的商品属性相似的商品
重点:相似系数计算公式:相同属性位个数/总属性位个数
一、按评分倒序,查询会员浏览过的商品
$sql = "SELECT t1.member_code,t1.goods_code,t1.score,t2.goods_code,t2.goods_name,t2.shopping_guide,t2.market_price,t2.wbiao_price,t2.sale_type,t2.promotion,t2.models,t2.products,t2.image_url,t3.property_prefix,t3.properties FROM rc_member_goods t1 LEFT JOIN sj_goods t2 ON t2.goods_code=t1.goods_code LEFT JOIN rc_goods_properties t3 ON t3.goods_code=t1.goods_code WHERE t1.score<1000 AND t1.member_code IN ('". implode("','", $memberCodes) ."') ORDER BY t1.score DESC LIMIT {$nums}";
注1:添加 t1.score<1000 条件是为了过滤恶意(或机器)的行为记录
注2:为了方便计算,这里的 $nums 默认取值为 2
注3:虽然这里 limit 2,但因为 rc_member_goods 和 rc_goods_properties 是一对多的关系,返回的数据行数会 >= 2;所以后面还要合并去重。
合并去重:
$return = []; while ($v = $records->fetch(PDO::FETCH_ASSOC)) { if (!isset($return[$v['member_code']])) { $return[$v['member_code']] = []; } if (!isset($return[$v['member_code']][$v['goods_code']])) { $return[$v['member_code']][$v['goods_code']] = $v; } else { $return[$v['member_code']][$v['goods_code']]['property_prefix'] += ','.$v['property_prefix']; $return[$v['member_code']][$v['goods_code']]['properties'] += ','.$v['properties']; } }
二、准备一下需要查询的属性前缀和需要排除的商品
// $records为上面查了出来的两条记录 $one = $records[0]; $memberCode = isset($one['member_code'])? $one['member_code']:0; $goodsCodes = []; $propertyPrefixs = []; if ($memberCode > 0) { // 会员的下过的订单的商品(后面有用) $goodsCodes = $this->getMemberOrderGoods($memberCode); } $temp = []; foreach ($records as $key => $value) { $temp[$value['goods_code']] = []; $goodsCodes[] = $value['goods_code']; // 上面合并出来的前缀,现在又拆开[笑哭] if (strpos($value['property_prefix'], ',') !== false) { $prefixs = explode(',', $value['property_prefix']); $propertyPrefixs = array_merge($propertyPrefixs, $prefixs); } else { $propertyPrefixs[] = $value['property_prefix']; } }
三、查询属性前缀相同的商品(注:前缀相同,说明大致相似)
$sql = "SELECT t2.goods_code,t2.goods_name,t2.shopping_guide,t2.market_price,t2.wbiao_price,t2.sale_type,t2.promotion,t2.models,t2.products,t2.image_url,t1.properties FROM rc_goods_properties t1 LEFT JOIN sj_goods t2 ON t1.goods_code=t2.goods_code WHERE t1.goods_code NOT IN ('". implode("','", $goodsCodes) ."') AND t1.property_prefix IN ('". implode("','", $propertyPrefixs) ."') AND t2.status=1 AND t2.shelf_status=1 AND t2.view_status=1";
注:$goodsCode 和 $propertyPrefixs 为上一步得出的值
四、循环处理对比商品完整属性,得出相似系数
while ($row = $list->fetch(PDO::FETCH_ASSOC)) { $goodsCode = $row['goods_code']; $properties = $row['properties']; unset($row['properties']); foreach ($records as $key => $value) { if (strpos($value['properties'], ',') !== false) { $vProperties = explode(',', $value['properties']); $row['similarity'] = 0; foreach ($vProperties as $p) { $row['similarity'] = max($this->genSimilarity($p, $properties), $row['similarity']); } } else { $row['similarity'] = $this->genSimilarity($value['properties'], $properties); } } }
相似系数计算公式:相同属性位个数/总属性位个数
private function genSimilarity($s1, $s2) { $arr1 = explode('|', $s1); $arr2 = explode('|', $s2); $same = 0; $total = count($arr1); foreach ($arr1 as $key => $v1) { $v2 = $arr2[$key]; if ($v1 == $v2) { $same++; } else { $t1 = explode(',', $v1); $t2 = explode(',', $v2); if (array_intersect($t1, $t2)) { $same++; } } } return $same/$total; }
五、过滤相似系数低的商品、按相似系数倒序排列
// $similarity 为你需要的最低相似度 if ($row['similarity'] >= $similarity) { if (isset($temp[$value['goods_code']][$goodsCode])) { // 因为多选属性拆分的问题,多个前缀可能对应的同一个商品;这里通过比较,取相似度最高的记录 if ($row['similarity'] > $temp[$value['goods_code']][$goodsCode]['similarity']) { $temp[$value['goods_code']][$goodsCode] = $row; } } else { // $value['goods_code'] 为原记录的商品, $goodsCode 为正在对比的商品 $temp[$value['goods_code']][$goodsCode] = $row; } }
private function sortAndFilter($arr) { $return = []; foreach ($arr as $k => $v) { if (!empty($v)) { $v = array_values($v); uasort($v, function($a, $b){ if ($a['similarity'] == $b['similarity']) { return 0; } return $a['similarity'] < $b['similarity']? 1:-1; }); $return[] = $v; } } return $return; }
六、按评分比例,取N个商品
$return = []; // 商品B的占比 $rate = 0; if (isset($records[1]['score'])) { $rate = $records[1]['score']/($records[0]['score']+$records[1]['score']); } // 商品B的截取个数 $num2 = intval($nums * $rate); // 商品A的截取个数 $num1 = $nums - $num2; foreach ($temp as $key => $value) { if ($key == 0) { $p = array_slice($value, 0, $num1); $return = array_merge($return, $p); // 商品A的相似商品可能不够 $num1 个,不够就补给 $num2 $num2 += $num1 - count($p); } elseif ($key == 1) { $p = array_slice($value, 0, $num2); $return = array_merge($return, $p); } } // 假设商品B没推荐商品或者总的推荐商品还是不够 $num 个 $has = count($return); if ($has < $nums) { $p = array_slice($temp[0], $has, $nums-$has); $return = array_merge($return, $p); }
七、其他
如果最终的推荐商品数量还是不及 $num 个,则考虑补充销量高的商品(随机)
上一节:(三)基于商品属性的相似商品推荐算法——批量处理商品属性,得到属性前缀及完整属性字符串
下一节:(五)基于商品属性的相似商品推荐算法——算法调优及其他