数学建模社区-数学中国

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

作者: 杨利霞    时间: 2022-9-8 10:21
标题: php+mysql实现简单的协同过滤推荐算法

  w4 l# n- R$ b- |! H2 mphp+mysql实现简单的协同过滤推荐算法
# N  n3 z1 J3 w$ w+ ?3 o* q' o1 F5 e仅做标记。。。7 `6 ?2 N0 r7 R4 o1 j
& w& F# O# g! o6 [( Q( |& e

6 M/ i. U$ \7 H) Q/ u' I0 G& Z+ v# D6 F, f1 j6 S/ B$ L* |8 ?
要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。. o" d1 q" ?* f% k; ^

2 T0 S9 h2 b2 F6 N! z, X6 G3 r) y% [算法核心的公式如下:5 C5 c: r9 [% w

3 p% J7 S5 c. e1 r# u2 p+ q1.余弦相似度(求邻居):
3 s) P/ s' O" r6 ?! p% P' }+ K& |' H# ~
2.预测公式(预测a可能会喜欢哪种物品):
6 S! }% n) c* E
& S+ X. l! L! ?仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。- T4 N& }/ c4 j5 e/ w9 d: |

7 s. z5 K9 g/ `首先建表:
! O/ s8 p7 S7 d: I1 j. ^, J: b8 ]/ v2 _
DROP TABLE IF EXISTS `tb_xttj`;; L  Z5 n! G, g- q9 R) I
CREATE TABLE `tb_xttj` (/ A$ o- L7 n5 T/ T. B5 h  `) [) a
  `name` varchar(255) NOT NULL,( W* w8 |& Q' H0 F' m! M0 m7 ~: Q
  `a` int(255) default NULL,2 [2 S. s; T* z! q
  `b` int(255) default NULL,& }* C+ A8 [" X; a: j! J4 {
  `c` int(255) default NULL,
" L. p$ ^* z1 S* S  `d` int(255) default NULL,
& J. T5 d5 m6 G  `e` int(255) default NULL,
5 H4 Z# h/ G  r" Y% M" W' y3 G  `f` int(255) default NULL,
4 F$ u/ R/ _  A+ G" h6 J  r  b  `g` int(255) default NULL,
& M7 K/ H: k4 u+ m* @  `h` int(255) default NULL,
) Y) @- Z2 h7 l( l- t4 d9 _  PRIMARY KEY  (`name`)% W/ L" n- [' v' a! _
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
: @  u4 L1 i" p
0 q4 e2 u* G# p6 ^8 ]7 N3 uINSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);7 i0 ?7 S8 n8 B. n' l7 x' f1 ]
INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);, ], v. |8 C* e5 a0 y- E
INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');/ g3 s9 X! N( c2 r6 {- }
INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');
9 t6 I4 ]( n6 RINSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');1 a, j! r" p6 U
INSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
! ~$ n7 D4 r! w% Q# Y! Y
7 a* j; o# ^& J8 c" l0 Y* e
) \0 J- G2 H& N5 n2 f2 k+ z2 V 我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
8 [; b! m. l& P, u# Z. |' T
$ D: @5 z8 t, N    用php+mysql,流程图如下:
! |- X! R# \3 s& T0 G) L' p5 I+ R8 _( ~
连接数据库并将其存储为二维数组的代码如下:
4 c% b5 Q  T- ]% U4 }* P
1 v. \3 _- S' G; bheader("Content-Type:text/html;charset=utf-8");
2 g) r1 [& ]0 a$ G+ U' d- E5 i2 z; X% o
mysql_connect("localhost","root","admin");
: J0 G: ~  x9 J3 Y2 K  j1 omysql_select_db("geodatabase");
2 W4 u- C- ~6 e7 V( G* umysql_query("set names 'utf8'");       
. G6 z, k- ~2 z. b
3 y5 q0 p% M1 ^4 k) W- Y$sql = "SELECT * FROM tb_xttj";
6 y& [, h6 H2 R; T7 m' [, K$result = mysql_query($sql);' [: n3 [8 e: {$ `" H& \
6 _& [/ J. X* v+ o$ w
$array = array();
1 G" S  x3 [& t% z6 Swhile($row=mysql_fetch_array($result))
# h0 p6 ?- `4 N! Y8 \{
3 {, Q$ t! w) ?# U9 ?        $array[]=$row;//$array[][]是一个二维数组) e2 Z1 |5 Z# a7 p" W
} % l7 C& v% ~" B5 \' W. f
% v. P7 G/ h4 ^* j, A0 P9 _  t
问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。2 }1 ~* ]' i% |/ x/ c' s0 G
2 O5 h& I) ~% @7 \+ z4 X" d" o
求Leo与其他人的Cos值代码如下:* u) s! Y. b' h( y8 V5 |; d
' z9 W; l+ I9 W& K/ ~/ Z
/*$ A$ p) Z/ k( D/ L' i# i* f0 W
* 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来8 J+ U) I3 k! Z! u, M* n! _3 F
*/* s- T2 P% c0 U; V( b+ ~' R( \
( n- Y1 R1 [' Q" a% |, u2 m
$cos = array();
5 W3 Q3 k; L% L" v+ V( O' \6 ]2 S$cos[0] = 0;
( J4 Z8 _. i9 O0 w1 w$ X6 I& O$fm1 = 0;
( H! o* o; I1 l$ y  T( g% j//开始计算cos
$ G, _4 G3 S  G; `4 t//计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容
8 E* r3 @! l7 p* Ffor($i=1;$i<9;$i++){
, _1 z* n# F, w4 I. s        if($array[5][$i] != null){//$array[5]代表Leo
6 j( e: K$ _  M2 \                $fm1 += $array[5][$i] * $array[5][$i];5 r/ W7 k3 s# |8 @3 f2 k0 \
        }" E5 R& G# ]# d5 G3 i! B; u
}
7 ?3 D1 v( D: L) |- Z" E: n& j% ^" T/ y7 E: r
$fm1 = sqrt($fm1);# Q# E0 _7 T4 ~

! l! H7 r0 n; O6 ~0 C% S/ Yfor($i=0;$i<5;$i++){
) M4 {5 H% m: O$ ~0 g: w        $fz = 0;( [  M: N1 n% h9 y, N
        $fm2 = 0;  N) k4 _' j1 E" ?2 e- N& p
        echo "Cos(".$array[5][0].",".$array[$i][0].")=";
( k3 j" Q5 q- P! ^% n! B1 P! Y       
1 T7 c' X% J8 b- W2 D7 y  x, ~        for($j=1;$j<9;$j++){
! L' a- Q( i* ]: `  a* _6 H7 j            //计算分子
, Q& B2 ?. |  W) D3 l                if($array[5][$j] != null && $array[$i][$j] != null){
7 Z, U( x9 Z6 I8 H1 l                        $fz += $array[5][$j] * $array[$i][$j];8 W8 Z* P2 v* V3 t6 Z; f" z: g% n
                }- W9 r/ t- o! |, ~# T% v0 C/ r
                //计算分母2$ b; R) D, E5 F# k4 |8 f
                if($array[$i][$j] != null){3 o& |! z. u5 S- t; u( m
                        $fm2 += $array[$i][$j] * $array[$i][$j];0 F: L" k. V$ ]% B* i0 N# p8 J
                }                       
( u8 {& Z- ^2 x! X% X        }
% C) n1 d  i$ T% ?  W        $fm2 = sqrt($fm2);8 J3 w" W8 F( U  L1 @) [1 M
        $cos[$i] = $fz/$fm1/$fm2;
' {! B/ N! N" y' Q& ]        echo $cos[$i]."<br/>";# d. L+ W2 ~* }/ M) [' E
}4 Z* O4 D% N* _' b  R  J1 T
2 q- c1 G" ^% |* B% G
这一步得到的结果是酱紫:* c; f, p! b! v, ?* u

) j, T2 j  C* V, F, l; n将求好的Cos值排序,采用快排代码如下(百度copy而来):
3 W( K( [/ x- h1 S, L3 ~% e. x, d* c5 _
( R. U9 |- `" U) F8 q. }2 Y6 R
//对计算结果进行排序,凑合用快排吧先
. F* a- y. C' I& r$ A  Pfunction quicksort($str){
2 V$ d1 O2 `+ {  G) W        if(count($str)<=1) return $str;//如果个数不大于一,直接返回
4 m0 n. q- u$ @' B9 @7 B        $key=$str[0];//取一个值,稍后用来比较;
$ R2 ~. \$ b( ?) [% [1 R7 m8 X        $left_arr=array();9 W3 `/ H$ u! |' X; L
        $right_arr=array();
$ s" j" n7 R- s; R( `        9 l+ X0 n7 }7 c& t; v3 Y4 \
        for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;3 |2 a  V7 ~' ?
                if($str[$i]>=$key)- `# K. B3 |5 a
                $left_arr[]=$str[$i];0 M" `0 L% {& l6 f( x
                else9 v! R! J. @( E0 y( o6 n. J/ S
                $right_arr[]=$str[$i];& Z, b4 n6 U0 \8 A& h* L# S+ Y
        }' t. }! j  Q& u1 s
        $left_arr=quicksort($left_arr);//进行递归;
" v2 `0 `7 u1 I6 ?5 w# L# ^        $right_arr=quicksort($right_arr);- I6 p" J8 I/ \. @
        return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;( B0 C8 D9 G2 B$ P  E) s, I+ A
}0 K0 r4 g2 ~( B$ N0 J9 ?1 C
! w9 ^. d2 L1 Q  l9 n: R2 B8 s
$neighbour = array();//$neighbour只是对cos值进行排序并存储2 x& q" p) C) d- y3 P( b
$neighbour = quicksort($cos);7 C) n6 q- Y, Z5 p+ v; B3 t4 C
6 G! S2 n) m1 s

3 K# Y  W- q8 c, B! R这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。; y; b, n% y8 Z& k% _  p

) U% ]0 Q% \4 g1 F选出Cos值最高的3个人,作为Leo的邻居:
3 q/ y, E% q1 \' u
, x# `9 O( f- A2 p( l//$neighbour_set 存储最近邻的人和cos值
- T% c. [- x1 f( J% L* A$neighbour_set = array();1 v. ]. \! V4 r- ^; `& p
for($i=0;$i<3;$i++){. Y) x8 Q6 [% ~2 y2 D
        for($j=0;$j<5;$j++){
% z! [7 B7 D- a                if($neighbour[$i] == $cos[$j]){
* J+ V) n! a' c7 ?' |2 J% G( P                        $neighbour_set[$i][0] = $j;
# W" C5 L: R2 }8 i  S3 t( s                        $neighbour_set[$i][1] = $cos[$j];. X+ |: k3 H- Z) G2 Q' P
                        $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分1 `. K+ o' h! J- f& a6 l
                        $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分* }/ ]8 |3 ?: \/ t) P% ^  R, o
                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
$ ]8 _% _! {/ S                }
+ V& K  T& z8 I: K  L3 r/ D        }
5 S3 p% }9 L6 C! k4 D}9 t5 p) R& q: Y( G9 H9 X. Y) S
print_r($neighbour_set);+ Q1 ?" n! S7 B* [; H; P
echo "<p><br/>";
# y1 c. B6 F# c4 H: ]3 o" Z8 g2 r. a+ m, l
这一步得到的结果是酱紫:
  e" m: N( X" @) }2 k+ b" p; t! p+ I& W- _' {
- n- h& T- K' H3 ^/ I! W7 U! [1 v
' O; E9 c1 K! e3 w5 ]) W7 T
转存失败重新上传取消
# D$ W: e( b" {' k/ X8 Q0 l. e5 ]9 u9 |* {
这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。" c  Z& ]5 ?! \+ e* h* q; V
+ B; A6 V9 j' A* L" r' ^
开始进行预测,计算Predict代码如下:$ h% q% O% e  v7 l9 U

4 X  O# K) O3 O5 o我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。/ C* ?. X* a' i4 N  T0 O

- k0 {* ^4 Y" O! v$ L  M  G//计算Leo对f的评分
+ `( d# C& a/ l0 ]$p_arr = array();
8 U# E1 p. p) x$pfz_f = 0;
! ]3 a; D: d: J2 F8 l$pfm_f = 0;
" p" y* g; o5 }2 Hfor($i=0;$i<3;$i++){
: o( T- ?; M) Y5 a; x+ u/ w6 c        $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
5 X. i4 \3 Q: E6 n, B# R        $pfm_f += $neighbour_set[$i][1];  I9 l; z( O7 r, o4 [! W3 h& j4 v/ f3 Q3 m
}
! A2 p0 F! Z1 g$p_arr[0][0] = 6;
7 |  C) t5 L1 x$p_arr[0][1] = $pfz_f/sqrt($pfm_f);* }' C2 h" y! s' F3 F" V: I
if($p_arr[0][1]>3){1 M- Q) C1 k: O& `4 G# q& H% ~/ I
        echo "推荐f";
. G& ~5 V% n2 y5 |' j}- a, t+ z. x7 J6 [7 j  Q
7 [0 B) H& I( P4 |; ?" a
//计算Leo对g的评分' D1 a( f+ @+ v1 z; Z- M6 b; u
$pfz_g = 0;# O# u1 H2 ?( A# _/ \
$pfm_g = 0;
% y, y0 S% G+ a  ifor($i=0;$i<3;$i++){
, N8 W) [! }0 f6 {        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
% L1 ^6 y( _/ L9 j! o# R        $pfm_g += $neighbour_set[$i][1];/ r3 ~& S3 b. O8 w4 N9 p6 f; k. |2 c
        $p_arr[1][0] = 7;
0 |9 S, Y. U( W' g' E( z, G        $p_arr[1][1] = $pfz_g/sqrt($pfm_g);" x" C: x+ E/ F8 G: P% b
}
  \; c- J1 ?1 K2 Y& ~if($p_arr[0][1]>3){2 f, p. a. A1 G7 I
        echo "推荐g";
4 N' F& I! a# Q8 x}
8 M0 ?. |# U6 S+ |# ], `6 s$ W- q1 E% p1 Z" n
//计算Leo对h的评分
( `3 z8 O' n) \. `: X' t4 ^$pfz_h = 0;8 }& ^+ S) z  Q9 q% _
$pfm_h = 0;) ]0 a* b. j1 j, f2 o7 N
for($i=0;$i<3;$i++){
( P4 b# M- v+ B# @        $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
" e) e: U5 U5 \5 s/ ]& S0 A+ R        $pfm_h += $neighbour_set[$i][1];
$ K+ x+ N, \, G5 k. P4 ?        $p_arr[2][0] = 8;
6 w4 Q9 v5 x( P8 g  _        $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
& s* g6 o1 b" r& W- |1 P}+ \/ V2 [8 i3 w% l! g7 M0 q
print_r($p_arr);5 j6 Q; l0 l; m3 G
if($p_arr[0][1]>3){9 `. {' @* G# W- T& `* A5 G7 z
        echo "推荐h";
+ k5 ~8 a) a* n}( L  ~* P7 O, `# c7 r6 |0 e& g
0 g) j2 T$ h6 T: m
$p_arr是对Leo的推荐数组,其内容类似如下;
; D" A# {* n  r+ M
- b) Y' v% e4 R2 d& j' J' EArray ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) ); y' K/ K: H* l  Y6 H0 Q
2 t  B" F1 w$ E/ e
f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........
) i* }6 S5 n% J  _' V3 R
+ ~5 I" f" g6 |( s6 J求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。  |. {) G) F4 W0 g* U6 Q: d
% ]# ]6 X' f/ Q- S4 D1 v& c
从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
, M# a' l; b$ W' N
# y$ V+ h5 e/ B: S: l5 M2 j1 T1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
0 g$ p% {, f: C2 Y) O/ I, q
1 Y- U: Z: ]6 Q+ U0 h1 k9 w2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。, m; f+ u7 I) C# W$ n  J1 b

% v3 ]9 M3 S& \7 _' F/ E3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
- N$ T) H0 H+ ?8 X, c, A
2 F: G7 v9 Z! o9 c1 X% P7 _4.可以适当引进基于内容的推荐,来完善推荐算法。- }/ ~6 M3 J: U/ z
7 ?! b, Q: l8 Q% Q! {1 V
5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。* }4 p  m) F" n% F( J: E
————————————————. i% p' v5 i/ |1 X* R
版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 U" a$ \/ f0 Q4 q6 w& j) A
原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
+ K2 w3 |& S6 G  s. G6 r0 l
7 c' R, R  `- L% l# [2 i# f. q' R  u, Q( \0 R6 N- z, O# v' ]. v$ q





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