(四)基于商品属性的相似商品推荐算法——推荐与评分高的商品属性相似的商品

系列随笔:

(总览)基于商品属性的相似商品推荐算法

(一)基于商品属性的相似商品推荐算法——整体框架及处理流程

(二)基于商品属性的相似商品推荐算法——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 个,则考虑补充销量高的商品(随机)

 

上一节:(三)基于商品属性的相似商品推荐算法——批量处理商品属性,得到属性前缀及完整属性字符串

下一节:(五)基于商品属性的相似商品推荐算法——算法调优及其他

 

posted @ 2020-03-12 18:09  Tiac  阅读(1293)  评论(2编辑  收藏  举报