日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2025/7/14 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TP5 实现基于标签简单的推荐算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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 实现基于标签简单的推荐算法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。