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

系列随笔:

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

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

(二)基于商品属性的相似商品推荐算法——Flink SQL实时计算实现商品的隐式评分

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

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

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

 

 

2020.04.15  补充:协同过滤推荐算法.pptx

 

提取码:4tds

 

 

批量处理商品属性,得到属性前缀及完整属性字符串


 

一、查询全部商品($lastCode默认0,$limit默认0)

$sql = "SELECT goods_code FROM sj_goods WHERE goods_code>{$lastCode} ORDER BY goods_code ASC" . ($limit>0? " LIMIT $limit":"");

注1:如果商品数量较多,建议配合 $lastCode 和 $limit 做分批处理

注2:查询结果不要直接转数组,用游标操作效率会好一点

 

二、每100个商品批量查询处理商品属性

$list = $this->db['seller']->getAll($sql);
$goodsCodes = [];
$count = 0;
while ($row = $list->fetch(PDO::FETCH_ASSOC)) {
    $count++;
    $goodsCodes[] = $row['goods_code'];
    if ($count%100 == 0) {
        // 每100个商品查询一次属性
        $properties = $this->genGoodsProperties($goodsCodes);
        // 属性入库
        $this->insertGoodsProperties($properties) or die($goodsCodes[0].' 异常!!');
        $goodsCodes = [];
    }
}

// 别忘了不够100个商品的情况
if (!empty($goodsCodes)) {
    $properties = $this->genGoodsProperties($goodsCodes);
    $this->insertGoodsProperties($properties) or die($goodsCodes[0].' 异常!!');
}

 

三、具体的属性查询及处理(上面的 genGoodsProperties 方法)

1)批量查询商品的属性code及属性value

$sql = "SELECT t1.goods_code,t1.brand_code,t4.property_name,t3.property_code,t3.property_value FROM sj_goods_product t1 LEFT JOIN sj_product t2 ON t2.product_code = t1.product_code LEFT JOIN sj_style_properties t3 ON t3.style_code = t2.style_code LEFT JOIN sp_product_property t4 ON t4.property_code = t3.property_code WHERE t1.goods_code in ('". implode("','", $goodsCodes) ."')";

注1:主要就是要把商品对应的所有属性值都查询出来,根据自己的项目数据库设计来操作就好

注2:为了提高商品的相关性,这里我还查询了 brand_code,这个稍候再说怎么用

 

2)确定要用哪些属性组合来做前缀,以及完整的属性组合

// 属性前缀为:适用人群-佩戴场合-机芯类型-价格区间-表盘形状-表盘直径,后面的属性排列顺序可以随意
$needs = [31=>'适用人群', 40=>'佩戴场合', 1=>'机芯类型', 39=>'价格区间', 9=>'表盘形状', 11=>'表盘直径', 13=>'表盘刻度', 17=>'表带材质', 14=>'表盘颜色', 25=>'防水', 3=>'外壳材质', 38=>'表盘宽度', 3=>'表盘厚度', 12=>'镜面材质', 16=>'表壳底盖', 19=>'表带颜色'];

注1:数组的键为属性的code,值为属性的名称

注2:为何这样选以及怎么优化,后面会在  (五)基于商品属性的相似商品推荐算法——算法调优及其他 里做说明。简单来说,属性前缀一样的商品,就是有40%-50%相似的

 

3)循环处理商品,利用 1) 和 2) 的数据读取商品属性值,拼接属性组字符串

private function genGoodsProperties($goodsCodes)
{
    // 属性前缀为:适用人群-佩戴场合-机芯类型-价格区间-表盘形状-表盘直径,后面的属性排列顺序可以随意
    $needs = [31=>'适用人群', 40=>'佩戴场合', 1=>'机芯类型', 39=>'价格区间', 9=>'表盘形状', 11=>'表盘直径', 13=>'表盘刻度', 17=>'表带材质', 14=>'表盘颜色', 25=>'防水', 3=>'外壳材质', 38=>'表盘宽度', 3=>'表盘厚度', 12=>'镜面材质', 16=>'表壳底盖', 19=>'表带颜色'];
    $properties = $this->getGoodsProperty($goodsCodes);
    $return = [];
    foreach ($goodsCodes as $goodsCode) {
        $prefix = '';
        $full = '';
        foreach ($needs as $key => $value) {
            $brandCode = 0;
            // 这里的 $properties 是第1)步得到的,已转换成了map
            if (isset($properties[$goodsCode][$key]) && !empty($properties[$goodsCode][$key])) {
                // 如果商品有此属性就拼接属性值
                $full .= $properties[$goodsCode][$key]['property_value'].'|';
                $brandCode = $properties[$goodsCode][$key]['brand_code'];
            } else {
                // 如果商品没有这个属性,或属性值为空,那拼接一个 0
                $full .= '0|';
            }

            // 临界点(拿到属性前缀)
            if ($value == '表盘直径') {
                $prefix = $full;
                // 加入品牌信息
                $full .= $brandCode.'|';
            }
        }

        // 截取掉最后多余的 |
        $prefix = substr($prefix, 0, -1);
        $full = substr($full, 0, -1);
        // 拆分多选属性
        $prefix = $this->splitProperties($prefix);
        $full = $this->splitProperties($full);
        $return[$goodsCode] = [$prefix, $full];
    }
    return $return;
}

 

四、属性半成品示例

例如:有两个商品的属性是这样的(点击放大查看)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

经过第三步的处理,它的属性组值是这样的:

商品A:

$prefix = '96|297,300|195,41|64|46|218';
$full   = '96|297,300|195,41|64|46|218|87|220,222|51,242|58|185|198,200|41mm|12mm|276|257';

商品B:

$prefix = '96|298,297,299|195|64|46|218';
$full   = '96|298,297,299|195|64|46|218|87|220,222|242|58|185|200|41mm|L619/888|276|257';

注:逗号隔开的属性值表示多选属性,例如商品A的佩戴场合为"运动、商务休闲"(297,300),商品B的佩戴场合为"商务休闲、时尚、正装"(298,297,299)

通过观察属性组,可以看出,其实两个商品是相似的(大部分属性位的值相同);但因为多选属性的影响,在使用属性前缀查询相似商品的时候,不太好办,就是有那么一丢丢不相同(上面示例红色字体部分)

 

五、拆分多选属性

首先,我们先要想一下自己想得到什么,然后再想办法实现。

例如,我想像中商品A的属性前缀的拆分情况是这样的:

观察上图可得到:

第一步,原属性位 “297,300”,被替换成了“297”和“300”,其他属性位不变,得到两个新属性组字符串;

第二步也一样,两个新属性组字符串的属性位“195,41”,被替换成“195”和“41”,其他属性位不变;

很明显,这就是一个很简单的字符串替换过程,关键是怎么维护和保存原字符串

实现代码如下:

private function splitProperties($propertyPrefixs)
{
    // 没有多选属性,直接返回
    if (strpos($propertyPrefixs, ',') === false) {
        return [$propertyPrefixs];
    }

    // 原属性组(也是结果)
    $origin = [$propertyPrefixs];
    $return = '';
    // 拆分属性位
    $arr1 = explode('|', $propertyPrefixs);
    // 循环属性位
    foreach ($arr1 as $k => $v) {
        // 多选属性位
        if (strpos($v, ',') !== false) {
            $new = [];
            // 拆分多选属性
            $arr2 = explode(',', $v);
            foreach ($arr2 as $kk => $vv) {
                // 循环原属性组,替换原属性位
                foreach ($origin as $kkk => $vvv) {
                    $prefixs = str_replace($v, $vv, $vvv);
                    // 替换结果
                    $new[] = $prefixs;
                }
            }
            // 更新的原属性组
            $origin = $new;
        }
    }
    return $origin;
}

 

六、最终结果展示

商品A:

 

商品B:

 


 

上一节:Flink SQL实时计算实现商品的隐式评分

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

 

posted @ 2020-03-12 18:08  Tiac  阅读(1149)  评论(0编辑  收藏  举报