推荐相似度的简单实践

来源:互联网 发布:sql insert 编辑:程序博客网 时间:2024/04/29 01:52

项目中有个模块是读书分享,为了提高粘度,参考了豆瓣等原型后,决定也加上相似度推荐的功能。当然,因为才疏学浅,加上理解不深,做出来的肯定没有豆瓣的强大,但胜在有个雏形,以后也可慢慢扩展。

关于这个,如果对推荐算法不是很理解的朋友,建议可以先看看某大神的博文,当然本博客也有转载【使用欧几里德距离构建简单的推荐系统计算用户相似度】,程序的基本思路便是因此得来的,在本文中不再多说这个算法。


记得那个时候找例子,网上很少php的例子,因此自己写一个,如果能帮到有需要的朋友,便心满意足了,有任何不足之处也欢迎指正。


废话完毕。


本例子的应用是在图书的详细页的底部给出相似书籍推荐,因此在图书详细页程序里,加上一个获取相似度的函数

//推荐$similar = read_get_similar($id);

我已封装为函数,$id是书籍的id。这个函数返回的是相似的书籍信息的二维数组

下面我们看这个函数:

/**  * 获取$bookid相似口味的前三个用户,并取得他们也喜欢的书籍  * @param int $bookid 图书id  * @return array 书籍数组  * @author mrz  * @version 1.0 @2012-4-19  */ function read_get_similar($bookid) {     $data = get_similar_data($bookid);     $temp = $return = array();     $count = count($data);     for ($i = 0; $i < $count; $i++) {         //保持索引并取出一个用户基数         $user1 = array_slice($data, $i, 1, true);         $uid1 = implode('', array_keys($user1));         $user1 = array_shift($user1);         //交叉对比其他用户         for ($j = 0; $j < $count; $j++) {             $user2 = array_slice($data, $j, 1, true);             $uid2 = implode('', array_keys($user2));             if ($uid1 == $uid2) {                 continue;             }             $user2 = array_shift($user2);             //获取这两个用户的相同评分书籍条目,与相似度             $cs = getCommonItems($user1, $user2);             //如果有共同条目,计算相似度这两个用户的相似度             if ($cs[0] > 0) {                 $sim = sqrt($cs[1] / (double) $cs[0]);                 $sim = 1 - tanh($sim);                 $maxCommonItems = min(count($user1), count($user2));                 $sim = $sim * ((double) $cs[0] / (double) $maxCommonItems);                 $temp[$uid1 . ',' . $uid2] = $sim;             } else {                 $temp[$uid1 . ',' . $uid2] = 0;             }         }         unset($data[$uid1]);         $count--;         $i = 0;     }     if (empty($temp)) { //数据不足,返回空         return $return;     } else {         $index = 0;         asort($temp, SORT_NUMERIC); //排序取相似度最近         foreach ($temp as $k => $v) {             if ($v !== 0 && $index < 3) { //排除掉没有相似度的并取前三个                 $index++;                 $str .= $k . ',';             }         }         $str = trim($str, ',');  //去除掉重复用户         $str = implode(',', array_unique(explode(',', $str)));         $query = DB::query("SELECT b.bookid,b.bookname,b.cover FROM " . DB::table('book') .                         ' b INNER JOIN ' . DB::table('read_record') .                         " rr ON b.bookid = rr.bookid WHERE FIND_IN_SET(rr.uid,'$str') AND rr.rating>3 GROUP BY rr.bookid ORDER BY rr.time DESC");         while ($row = DB::fetch($query)) {             $row['short_bookname'] = cutstr($row['bookname'], 25);      //不包含自身             if ($row['bookid'] == $bookid) {                 continue;             }             $return[$row['bookid']] = $row;         }         if (count($return) > 9) { //如果多于9本,取前9本             $return = array_slice($return, 0, 9, true);         }         return $return;     } }


这个函数里引用有获取相似数据的函数get_similar_data,函数如下:

function get_similar_data($bookid) {     $return = array();     //查找5个阅读过该图书并评分大于3星的用户     $query = DB::query("SELECT uid,bookid FROM " . DB::table('read_record') .                     " WHERE bookid = $bookid AND rating > 3 GROUP BY uid ORDER BY time LIMIT 5");     while ($row = DB::fetch($query)) {         //查找这5个用户阅读过的书籍的id与评价         $query2 = DB::query("SELECT bookid,rating FROM " . DB::table('read_record')                         . " WHERE uid = $row[uid] AND bookid!=$row[bookid] LIMIT 5");         while ($row2 = DB::fetch($query2)) {             $return[$row['uid']][$row2['bookid']] = $row2['rating'];         }     }     return $return; }

第一个函数里还引用到获取相同条目的函数getCommonItems,如下:

/**  * 取得两个用户之间相同的图书数量,计算他们对于每本书的评分差值  * @param array $user1 第一个用户数据数组  * @param array $user2 第二个用户数据数组  * @return array 包含有共同图书,评分差值的数组  * @author mrz  */ function getCommonItems($user1, $user2) {     $commonItems = 0;     $sim = 0;     foreach ($user1 as $key1 => $value1) {         if (isset($user2[$key1])) {             $commonItems++;             $sim+=pow($value1 - $user2[$key1], 2);         }     }     return array($commonItems, $sim); }


这个例子就这样结束了。之所以说它简单,是因为三个函数就能基本实现这么一个很有趣的功能。另外我也知道这几个函数写得不怎么样,尤其是对时隔大半年后、看完代码大全的我来说,觉得可以优化的地方实在太多太多。但考虑了一下,决定还是不改动,原汁原味的放上来。(当然,实际项目中肯定会改进的)


1是因为作为示例,有点基础的朋友应该可以看懂了。


2是作为技术水平进步的见证,我得看看以前写的是什么代码,呵呵。


如果要实际测试,必须得要大量数据,而且根据算法来说,是数据越多相似度越精确的。



原创粉丝点击