系列隨筆:
(二)基於商品屬性的相似商品推薦算法——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:
下一節:(四)基於商品屬性的相似商品推薦算法——推薦與評分高的商品屬性相似的商品