数学建模社区-数学中国

标题: php+mysql实现简单的协同过滤推荐算法 [打印本页]

作者: 杨利霞    时间: 2022-9-8 10:21
标题: php+mysql实现简单的协同过滤推荐算法
) u; s' }( o# E, l* w, P
php+mysql实现简单的协同过滤推荐算法7 a; c% C# F+ U% x  u7 P; B5 ]/ g
仅做标记。。。: T+ w% F! R2 C5 ?$ u
2 G$ U; I4 _  I/ ?9 s, ]  v

" L) m5 s# Y2 G# z: X! p& v' V9 L+ B
要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。9 F  e2 q% s3 Y5 I2 M& v) [$ Y
, T6 w  ^! N5 s
算法核心的公式如下:
/ ?) z! {, p/ T7 L1 E
; c# S* S3 L! q: Z9 }. p. d1.余弦相似度(求邻居):5 ?5 m$ S$ z3 s# l- b' b

2 _/ a+ e1 F6 k& Z( \2.预测公式(预测a可能会喜欢哪种物品):. o! r1 o! C* W' C0 \
9 K1 w) S' `! t% l: Y
仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。- V3 H* ?! J% H% w
* H" V; A) R1 d9 b: ~
首先建表:$ N$ {- m2 K5 A
6 L3 _  J  ]# \; S2 M4 _0 w" |
DROP TABLE IF EXISTS `tb_xttj`;
# E$ q# q+ }% y& j4 X$ D  [CREATE TABLE `tb_xttj` (1 B6 _2 L+ w6 Y
  `name` varchar(255) NOT NULL,
/ {0 C! E) F3 S# g* L+ Y; e2 }  `a` int(255) default NULL,  B6 m% y9 q8 D! n
  `b` int(255) default NULL,1 L+ h- m* @$ C: D& ]) g
  `c` int(255) default NULL,' x! v; j: g/ x% Q1 l9 ]
  `d` int(255) default NULL,
- p* c: F+ `# @% s5 j" h9 j  `e` int(255) default NULL,
  B$ p. [5 j4 L1 e. k8 D: n  `f` int(255) default NULL,
( W! j4 @7 i8 C9 n9 ^. E  `g` int(255) default NULL,& _: _; T5 e: e1 K" z
  `h` int(255) default NULL,& c  q+ B: [7 w7 S& y
  PRIMARY KEY  (`name`)- K3 m; e' b7 j  h/ d
) ENGINE=MyISAM DEFAULT CHARSET=latin1;9 V7 _; {6 q& O& ^! S% p7 W

/ u) u( j+ G! t) vINSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
8 I& \6 p/ s3 S$ \+ `INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);' z  d8 V: b2 B' a0 M* H/ i
INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
) F5 L/ R- R7 z* S/ O2 [INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');% u% O. N2 t2 s6 F
INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
8 O% J0 D. N, L, QINSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
# s2 C& |7 Y& @+ T
7 s6 l% d8 Y( c$ h6 b! S2 C7 y. o! N% S( Z' o
我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。% U; e. L( o% d% c1 w

7 p9 x9 ?5 O7 @3 @; C2 J$ i    用php+mysql,流程图如下:0 d$ G2 K! o0 r8 a+ R9 [$ Y

1 U. T0 ], W5 t4 Y$ M$ {: \5 J连接数据库并将其存储为二维数组的代码如下:6 E0 j4 s5 R7 Z

3 E  t7 P/ E) d! |$ r) eheader("Content-Type:text/html;charset=utf-8");
, G1 G$ [! D1 H7 i8 R% h! U- h; r1 q3 z, m7 R) K
mysql_connect("localhost","root","admin");+ I& d8 a) u( D  L; p; D" ?: b2 H
mysql_select_db("geodatabase");* B* c3 M- X1 s" K& p
mysql_query("set names 'utf8'");       
) B% X1 J0 W' W4 ~, W$ Z) k5 {1 ]9 D5 V0 F. |. M
$sql = "SELECT * FROM tb_xttj";. s5 |, N1 |7 {  G
$result = mysql_query($sql);
  C6 t$ ~$ H# ]
% O. g& P) b1 s; m8 s, r. q" g. t$array = array();/ Z! Y  R5 T( A% M6 b- ^' x
while($row=mysql_fetch_array($result))
& q1 B/ E4 v7 o0 F& {7 k( o5 @( r{
  K2 P: }; W' R2 V" N: Z$ U        $array[]=$row;//$array[][]是一个二维数组
' B$ z6 l0 A, o; v$ V} * R+ U7 x& b5 h7 O& d
4 z% |2 T7 I6 V* L
问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。/ I9 E0 I2 x& T5 R. t: ?
. z- R' J3 l$ ?* J+ S4 Y6 Q4 A
求Leo与其他人的Cos值代码如下:
( j9 k( R* p7 e( h1 S( Q4 x  I8 q3 V, j* C
/*
6 k, N, X4 O& x& {2 F; s * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来% X' a0 Q8 M7 i2 A* Z- u
*/: `- n9 d8 X- X6 m% }
- _' E9 r6 k" e! @
$cos = array();
/ s2 [+ `2 l2 l! y$cos[0] = 0;
# r, k: ?( S. M' A: p  n9 E$fm1 = 0;
$ J, z2 n" R6 w2 R; V* P//开始计算cos
' n0 J4 k- k- c1 p//计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容, {# J$ d9 z! x  x% `7 P! t
for($i=1;$i<9;$i++){& j+ @+ D0 u" q! M7 `) c
        if($array[5][$i] != null){//$array[5]代表Leo3 D3 i$ {' H+ `( c3 }, g
                $fm1 += $array[5][$i] * $array[5][$i];
, _4 d+ t& A0 S5 H9 N% e9 F        }
" H7 Z8 N0 p6 m5 P3 B' h}: ^3 g3 g6 F" S; P2 O5 V/ R$ V

$ w! C6 S+ L; w& d: S$fm1 = sqrt($fm1);
$ N4 ~! Q; ]3 e2 m9 R5 g' L0 \/ {9 ~; Y3 C& y0 w
for($i=0;$i<5;$i++){
- J# i5 V" s: s# [        $fz = 0;
- I& ]( X) f! T; r        $fm2 = 0;
$ O7 v6 w1 W! G7 O9 M, K0 T1 \" t3 W        echo "Cos(".$array[5][0].",".$array[$i][0].")=";/ U4 c, L/ ^6 h
        & y+ h" z( E- L
        for($j=1;$j<9;$j++){$ e9 `  K+ x9 o: j- O0 x1 t* w' y
            //计算分子6 U/ f$ U6 m3 Q  V/ t
                if($array[5][$j] != null && $array[$i][$j] != null){
7 D8 R& u. B0 y6 a/ G+ Z9 P7 a                        $fz += $array[5][$j] * $array[$i][$j];
' o/ n. p2 u- n                }- P8 W2 U) F. @
                //计算分母2
5 r4 z& D' M2 K                if($array[$i][$j] != null){) j$ [# H5 T# K+ s
                        $fm2 += $array[$i][$j] * $array[$i][$j];
8 q3 f4 Q( S. G/ ]                }                        9 K& |( T, ]4 S6 L6 T/ a# y& U
        }
3 O5 O: H5 X! B" j8 L        $fm2 = sqrt($fm2);
1 [" ]7 ~  {" i+ V        $cos[$i] = $fz/$fm1/$fm2;
5 o, x% B9 [' f' x' Q        echo $cos[$i]."<br/>";
) y) f' q/ Y0 V: ?3 l0 T+ V}
1 Q4 i! I9 i! ^8 c3 C
! H. q3 W* p1 E$ @( E. A  V这一步得到的结果是酱紫:2 N/ o* U3 G, [& E
! r. K2 c3 q* i
将求好的Cos值排序,采用快排代码如下(百度copy而来):
  l% `( n+ L7 t
* \& E8 w4 |/ m  b. X  m$ C5 m
8 ^) V. G0 D4 R( J' ^//对计算结果进行排序,凑合用快排吧先
# r; }; P# ]7 v. h, C+ Ffunction quicksort($str){# `  k9 P# @; n$ Q3 O2 W" N
        if(count($str)<=1) return $str;//如果个数不大于一,直接返回
8 C2 D' w* f0 q        $key=$str[0];//取一个值,稍后用来比较;
9 b4 G$ z( g% t' T. G/ f. u. g        $left_arr=array();  |5 e: ~1 J0 [  o5 @( B% L
        $right_arr=array();
" v5 A: U- r! g: B$ n       
: x6 e/ i- d1 V5 d* r- C* ^        for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;
& j; s! }! Y% s$ i1 e% ^; H) Q                if($str[$i]>=$key)
8 P; B- Z4 j, _- o4 [) }8 J" i2 }4 U7 o                $left_arr[]=$str[$i];& ]( G0 [+ `' R5 V" Q
                else  l; a! h$ V, f
                $right_arr[]=$str[$i];, d, Q  W& ~3 x* e, A! V
        }
: b- C# X* U7 V# s* I% L; R1 \% x        $left_arr=quicksort($left_arr);//进行递归;% ?7 b; b/ j5 N0 D3 F
        $right_arr=quicksort($right_arr);
0 X, x; E8 g/ W& e        return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;5 S  y  ?+ U/ Z( B+ i" h+ H9 s
}5 }+ M. B( g4 S3 v& P3 ?% A

! D4 j& d2 R' E- a+ W$neighbour = array();//$neighbour只是对cos值进行排序并存储( }0 I# g* V* r- ^/ [6 _
$neighbour = quicksort($cos);
% b7 R, s& E' V7 O/ o& ?* d' I6 r
# W: S: r- g( i# @) Q; ^
, g: R. V; }2 Z, }+ n$ {$ \8 ~这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
7 {" D0 t" L! e% _0 W
! p+ f7 O. l; e& }0 x选出Cos值最高的3个人,作为Leo的邻居:
7 g7 w& c- _- F+ u$ A
- P/ a) h6 D8 M: D& |//$neighbour_set 存储最近邻的人和cos值
! n# Q$ k3 P  b0 Y& {* h* [! M' M$neighbour_set = array();5 y3 y1 m. x" `+ p
for($i=0;$i<3;$i++){
, |3 v+ v4 N$ s$ H4 u        for($j=0;$j<5;$j++){" c- ^1 Y: p" _3 G0 c
                if($neighbour[$i] == $cos[$j]){8 t. P, K4 l/ f8 W8 p- i2 n/ k" R
                        $neighbour_set[$i][0] = $j;+ c- U9 F/ i- N5 p
                        $neighbour_set[$i][1] = $cos[$j];
8 H: C2 F6 c" ~6 [. q5 i$ p! \                        $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
9 D7 n( h1 _) I. r( U6 [% O( t                        $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分7 h$ }8 G9 g- L# t" U3 K5 N) z
                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分2 ]0 x/ v8 y$ k
                }
6 h# |) q" f. S. W* s+ `        }$ j+ s4 v8 U* ]! v! s
}
: k7 |6 W; C( w) E4 y7 eprint_r($neighbour_set);( H( j$ w2 Y2 {4 e- P& [/ G3 ^
echo "<p><br/>";
* G7 w( A# K  N1 e2 \
% }$ m( R+ `% s& Y# m- |% \这一步得到的结果是酱紫:+ Z9 a; V  I) a. W3 X

2 T# U# p5 Q% A# T  m/ E- \7 {
9 A* a$ A* o0 |: A
/ o- l3 U+ Y! `" ^转存失败重新上传取消
2 n$ ?4 W% m4 J6 N' ?3 C6 b$ p8 [& F0 S4 j8 v" C5 i$ h
这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。
7 G7 ?+ l# J; e, r9 \) U) c! `4 e' [! }7 C8 C
开始进行预测,计算Predict代码如下:
8 r( @! V* m3 I# U1 O
0 _) o: C" n& I9 d3 g; Z我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
: P& `" |7 e' K0 q" \7 D- |* f3 m. r- z$ \- M
//计算Leo对f的评分2 S3 s" B. c3 Z" D$ `; j
$p_arr = array();
( M$ x& T7 S$ Q7 Q' b1 F$pfz_f = 0;$ h0 n& \9 a# |3 ]$ `
$pfm_f = 0;* c0 o+ }7 w1 X7 P6 \1 V
for($i=0;$i<3;$i++){, P9 Q9 V- [. I7 W% u: v8 i
        $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];: g; _1 C, E. h* F
        $pfm_f += $neighbour_set[$i][1];
" ~) e6 E; V; N" B; k, H5 b& E' x+ J}: j* J/ P* Z$ T
$p_arr[0][0] = 6;1 k" P% ~" Z8 ~, l6 Y6 n
$p_arr[0][1] = $pfz_f/sqrt($pfm_f);
9 g) U  L8 [! i) q5 yif($p_arr[0][1]>3){
$ b7 a: y- ~' @" b7 \7 J5 W        echo "推荐f";, W/ ?8 `7 v" T: _/ e
}
1 i3 B- f$ Z- W, i* z& f9 o' J5 b9 i  {- f/ f4 m
//计算Leo对g的评分" E4 z( I' |) J6 e
$pfz_g = 0;0 V8 ]; T0 M$ j$ f
$pfm_g = 0;
* o  z8 ?% r9 r, Tfor($i=0;$i<3;$i++){
9 Q$ C9 K$ Q7 I) A7 A        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];4 `" ]1 }- F% v2 a. s! O
        $pfm_g += $neighbour_set[$i][1];8 d6 @- I8 |" H9 W
        $p_arr[1][0] = 7;
9 {6 V$ Q5 P, ^" I" h! p        $p_arr[1][1] = $pfz_g/sqrt($pfm_g);
9 b1 R' G! j4 k}7 v3 Z8 ]1 N; c) a3 D2 ^6 z7 w/ P
if($p_arr[0][1]>3){& m# i9 n; D8 E4 \* b7 X2 y2 v% i
        echo "推荐g";" i4 X% s- j) A. V% M+ ?6 H- `( r
}% I) J# _4 P$ g2 H$ ~, Y1 V& i: t
3 O" A- O  G, t* E  q+ ]
//计算Leo对h的评分$ J/ s# _$ D; b1 e, D
$pfz_h = 0;
) s9 x, f* ]! F+ F- l$pfm_h = 0;' I1 s- B' ~8 c( j- s% U, ^, }+ @) A
for($i=0;$i<3;$i++){0 t5 U: U; q2 F6 E
        $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];; k$ W" K( j+ R) @* E* L. S
        $pfm_h += $neighbour_set[$i][1];
. Y5 I( P  z5 j7 f, c        $p_arr[2][0] = 8;
. ~  Z6 {1 E# _: W0 @9 s6 Z4 n        $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
+ B" a: ^2 F5 i$ M# g8 G}/ r+ D! T0 U7 n) d% W
print_r($p_arr);
7 ^# \  N( f0 k. b! O. nif($p_arr[0][1]>3){- s  m' V4 t& p! W$ G0 E
        echo "推荐h";
6 {- C/ a& H3 H}
9 y2 p3 L& V' D! [  {9 B
$ x0 [8 j2 D; V" k3 o: P" `$p_arr是对Leo的推荐数组,其内容类似如下;
  `% r4 R3 g5 B& J6 F9 L; q8 O& f8 V* \/ L5 x, ^
Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )0 ]6 X3 g( q( h& B0 @! E* P
, l6 }9 {( L- ?% }. z
f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........- H# v/ A+ |# t+ S9 D. I2 S
, N* T4 Q1 B- w: x/ b
求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。- O) @5 B0 `: R- R) ~. y

1 o, U2 g9 j; @- B  t从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
; p) U$ ]' S  p$ r* x& x# @: U6 l# Y) ^" Z; R
1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。' [7 E" S8 [+ M  s6 O$ u

) U" `7 f( ^, F! [, y* e2 Q, X# ]# S2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。
# C" ]- ]+ T0 o0 W- a: N2 e) M  R% g( U, N
3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
1 _' K" F. g1 Q2 L+ n7 O( \! G) G" K+ b  }$ q' n1 o8 f
4.可以适当引进基于内容的推荐,来完善推荐算法。+ f) j1 [8 C9 m, F
* V( O& ?" A9 P6 `
5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。5 c$ E. B0 L/ k* k
————————————————' P  M0 ~2 o3 I$ T1 X7 a
版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。1 [$ t0 a& L( R0 k
原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
: M; X' o5 l5 ^% M( x0 V7 H
# o/ ?, B1 M! A5 Z- m) c3 _3 m3 ^8 z0 H" ^1 R0 z1 @' W; Q# T





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5