1. 程式人生 > >基於Tags的簡單內容推薦的實現

基於Tags的簡單內容推薦的實現

原來為了簡單方便,自己小網站上的文章頁的相關內容推薦就是從資料庫裡隨機抽取資料來填充一個列表,所以一點相關性都沒有,更本沒有辦法引導使用者去訪問推薦內容。

演算法選擇

如何能做到相似內容的推薦呢,礙於小網站還跑在虛擬主機上(對的,連一個自己完整可控的伺服器都沒有),所以可以想的辦法不多,條件限制在 只能用PHP+MySql。所以我想到的辦法就是通過Tags來匹配相似文章進行推薦。如果兩篇文章的TAGS 比較相似

比如:文章A 的TAGS為: [A,B,C,D,E]
文章B 的 TAGS 為:[A,D,E,F,G]
文章C 的 TAGS 為:[C,H,I,J,K]

通過眼睛我們能很方便的發現,文章B和文章A更為相似,因為它們有三個關鍵字相同分別為:[A,D,E],哪如何用計算機來判斷它們的相似度呢,這裡我們用jaccard相似度

的最基本應用來計算它們的相似度

jaccard相似度

給定兩個集合A,B,Jaccard 係數定義為A與B交集的大小與A與B並集的大小的比值,定義如下:

文章A和文章B的交集為 [A,D,E],大小為3,並集為[A,B,C,D,E,F,G],大小為7,3/7=0.4285...
而文章A和文章C交集為 [C],大小為1,並集為[A,B,C,D,E,H,I,J,K],大小為9, 1/9=0.11111...

這樣就可以得出文章A,B比文章A,C更為相似,有了這個演算法,計算機就可以來判斷兩篇文章的相似度了。

具體的推薦思想

給定一篇文章,獲取該文章的關鍵字TAGS,然後通過以上演算法去資料庫比對所有文章的相似度,獲取最相似的N篇文章進行推薦。

實現過程

第一 TAGS的獲取

文章的TAGS是通過TF-IDF演算法,提取文章中的高頻詞,選取N個作為TAGS,對於中文的文章來說還涉及到一箇中文分詞的問題,因為是虛擬主機的關係,這步的工作我用python(為什麼用Python ,jieba分詞,真香)在本地寫了一個程式,完成所有文章的分詞,詞頻統計,生成TAGS,並寫回伺服器的資料庫。由於本文是寫推薦的演算法,所以分詞和建立TAGS的部分就不具體展開了,而且不同的系統有不同的TAGS建立方式。

第二 TAGS的儲存

建立兩張表,用於儲存TAGS
tags,用於存所有tag的名稱


+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| tag   | text       | YES  |     | NULL    |       |
| count | bigint(20) | YES  |     | NULL    |       |
| tagid | int(11)    | NO   | PRI | 0       |       |
+-------+------------+------+-----+---------+-------+

tag_map 建立tag和文章的映身關係。


+-----------+------------+------+-----+---------+-------+
| Field     | Type       | Null | Key | Default | Extra |
+-----------+------------+------+-----+---------+-------+
| id        | bigint(20) | NO   | PRI | 0       |       |
| articleid | bigint(20) | YES  |     | NULL    |       |
| tagid     | int(11)    | YES  |     | NULL    |       |
+-----------+------------+------+-----+---------+-------+

tag_map存的資料類似如下:


+----+-----------+-------+
| id | articleid | tagid |
+----+-----------+-------+
|  1 |       776 |   589 |
|  2 |       776 |   471 |
|  3 |       776 |  1455 |
|  4 |       776 |  1287 |
|  5 |       776 |    52 |
|  6 |       777 |  1386 |
|  7 |       777 |   588 |
|  8 |       777 |   109 |
|  9 |       777 |   603 |
| 10 |       777 |  1299 |
+----+-----------+-------+

其實做相似推薦的時候,只需要用到tag_map表就可以了,因為tagid和tag name 是一一對應的。

具體編碼

1.獲取所有文章對應的TAGID


mysql> select articleid, GROUP_CONCAT(tagid) as tags from tag_map GROUP BY articleid;
+-----------+--------------------------+
| articleid | tags                     |
+-----------+--------------------------+
|        12 | 1178,1067,49,693,1227    |
|        13 | 196,2004,2071,927,131    |
|        14 | 1945,713,1711,2024,49    |
|        15 | 35,119,9,1,1180          |
|        16 | 1182,1924,2200,181,1938  |
|        17 | 46,492,414,424,620       |
|        18 | 415,499,153,567,674      |
|        19 | 1602,805,691,1613,194    |
|        20 | 2070,1994,886,575,1149   |
|        21 | 1953,1961,1534,2038,1393 |
+-----------+--------------------------+

通過以上SQL,可以一次性查詢所用文章,極其對應的所有tag
在PHP,我們可以把tags變成陣列。


public function getAllGroupByArticleId(){
        //快取查詢資料,因為這個是全表資料,而且不更新文章不會變化,便是每次推薦都要從資料庫裡獲取一次資料,對效能肯定會有影響,所以做個快取。
        if($cache = CacheHelper::getCache()){
            return $cache;
        }
        $query_result = $this->query('select articleid, GROUP_CONCAT(tagid) as tags from tag_map GROUP BY articleid');

        $result = [];
        foreach($query_result as $key => $value){
            //用articleid 做key ,值是該id下的所有tagID陣列。
            $result[$value['articleid']] = explode(",",$value['tags']);
        }

        CacheHelper::setCache($result, 86400);

        return $result;

    }

有了這個的返回結果,就比較好辦了,接下去的工作就是去應用jaccard相似度這個演算法了,具體就看程式碼吧。


/**
     * [更據指定文章返回相似的文章推薦]
     * @param  $articleid 指定的文章ID
     * @param  $top       要返回的推薦條數
     * @return Array      推薦條目陣列
     */
function getArticleRecommend($articleid, $top = 5){
        if($cache = CacheHelper::getCache()){
            return $cache;
        }
        try{
            $articleid = intval($articleid);
            $m = new TagMapModel();
            $all_tags = $m->getAllGroupByArticleId();//呼叫上面的函式返回所有文章的tags
            $finded = $all_tags[$articleid];//因為上面是包含所有文章了,所以肯定包含了當前文章。

            unset($all_tags[$articleid]);//把當前文章從陣列中刪除,不然自己和自己肯定是相似度最高了。

            $jaccard_arr = []; //用於存相似度
            foreach ($all_tags as $key => $value) {
                $intersect =array_intersect($finded, $value); //計算交集
                $union = array_unique(array_merge($finded, $value)); //計算並集

                $jaccard_arr[$key] = (float)(count($intersect) / count($union));
            }

            arsort($jaccard_arr); //按相似度排序,最相似的排最前面

            $jaccard_keys = array_keys($jaccard_arr);//由於陣列的key就是文章id,所以這裡把key取出來就可以了
            array_splice($jaccard_keys, $top);//獲取前N條推薦

            //到這裡我們就已經得到了,最相似N篇文章的ID了,接下去的工作就是通過這幾個ID,從資料庫裡把相關資訊,查詢出來就可以了
    
            $articleModels = new \Api\Model\ArticleModel();
            $recommendArticles = $articleModels->getRecommendByTag($jaccard_keys);
            CacheHelper::setCache($recommendArticles, 604800); //快取7天
            return $recommendArticles;
        } catch (\Exception $e) {
            throw new \Exception("獲取推薦文章錯誤");
        }
    }

雖然簡單,短短几條程式碼,但是效果還是可以的,推薦的文章有了一定的相似度,肯定可以帶來更好的使用者體驗,例項 ,你們可以看看 https://www.wx2share.com/Arti...

原文地址:https://segmentfault.com/a/1190000016389327