QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1920|回复: 0
打印 上一主题 下一主题

php+mysql实现简单的协同过滤推荐算法

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2022-9-8 10:21 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    / p8 C4 T# j. ~) {  M
    php+mysql实现简单的协同过滤推荐算法
    ) D$ S$ Y; w2 s仅做标记。。。0 u/ c6 e; N6 v! B" K
    4 |% a* k! j$ y

    3 S: |2 ~" |. k# \
    ; R0 l, U+ P. p8 b7 d: b8 x要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。% ]; n3 p$ X2 [) C

    7 P6 [5 L0 j# H* `* a算法核心的公式如下:
    # P$ q. M% E: J7 E$ d. j5 O! D' a4 K& |: \" p$ ^8 R- D9 a
    1.余弦相似度(求邻居):) j0 @9 O5 n8 n, L/ X; l6 a4 D1 U

    9 M# ?6 o8 z- @. ]& l' h$ k0 V2.预测公式(预测a可能会喜欢哪种物品):& ~- V  i) U) \& c4 J- x# q

    ' B/ k, }+ [" s; ~仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。9 H2 ^* t* M# n+ g

    + Y9 b0 U* C3 y首先建表:
    2 ~+ c4 v! d- D9 v1 z
    1 c8 `/ o% K5 V) D0 `DROP TABLE IF EXISTS `tb_xttj`;4 E( Z5 I/ q9 @2 _5 [, ], u' y
    CREATE TABLE `tb_xttj` (
    9 C3 z( Z" _  I8 a) f1 l' x  `name` varchar(255) NOT NULL,; [' p4 G( O& z6 o2 G
      `a` int(255) default NULL,
    1 c6 ~; [% }" \* m& u. D7 T  `b` int(255) default NULL,
    % ?* ^* ]- l6 U5 v$ l  `c` int(255) default NULL,
    4 P* ?; `' }4 Z  `d` int(255) default NULL,% k4 c, h" x6 A8 Y% l2 G6 J6 n1 X
      `e` int(255) default NULL,) @2 d$ r) Q7 e" O
      `f` int(255) default NULL,- b, i' X- C# ^2 e, H
      `g` int(255) default NULL,- f/ t( ?; K$ q6 X7 n
      `h` int(255) default NULL,
    . U9 T9 r  m1 k4 s5 T& T* D+ l  PRIMARY KEY  (`name`)
    ) K- L( F3 v2 Q; L( y2 I' D) ENGINE=MyISAM DEFAULT CHARSET=latin1;
    % b8 B% _) d0 v5 X  _' D% X  B
    & S4 ]6 s+ _0 r; H6 ~4 t8 zINSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
    ) c  U. O1 ~) \6 I2 EINSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);% ?" C- G* j9 N8 |) ?, \) R
    INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
    6 j2 V  C2 Q0 ~: N5 v  r# F" CINSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');: h& Y6 d' ^" {) H
    INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
    # z. ?7 D# K8 x1 B5 R9 v" J) jINSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);. ^% J2 e6 `! p" Y; K

    . X. p; B! ^7 e/ P9 I) k5 O  P/ d+ K+ t3 i! p0 p
    我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
    ; z* F( S" }  i+ R4 G  k
    + d( G3 {2 i! v2 l# f6 ?    用php+mysql,流程图如下:
    * U8 p& L# c" J. i# O3 {  Y% k# S, S( `. `9 P
    连接数据库并将其存储为二维数组的代码如下:/ p8 f2 R* @/ m; J
    2 F: j% d7 Y5 G4 O8 v9 m, c
    header("Content-Type:text/html;charset=utf-8");% Y" J+ N8 M' p& B8 }

    0 {& ]6 v% ~# P6 Mmysql_connect("localhost","root","admin");$ i, y) p8 V' Z. j
    mysql_select_db("geodatabase");
    6 J9 ^* |/ P+ e% x6 D* c& c2 @: }* Ymysql_query("set names 'utf8'");        5 x/ l" v. O; J6 K; ~

    , ?% E  w. S% n9 q& Q9 T  E$sql = "SELECT * FROM tb_xttj";5 j6 p, `" s; {' V# X7 P
    $result = mysql_query($sql);- a! A3 y4 B% Y  F

    ! R& Y6 |% e. ~3 B7 b$array = array();
    & U. d9 T0 B% c9 ewhile($row=mysql_fetch_array($result))6 a. m" t- }, ?2 [2 J
    {4 ?, d7 m7 V/ n/ ~
            $array[]=$row;//$array[][]是一个二维数组
    " Y  E5 P0 ~; {+ G( s}
    0 X! Y- l0 P" i: ~* ^" A) o6 i
    : `3 Z% d, q/ j# e0 A" M1 y/ t6 k问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
    : \% r: D9 j* X8 w
    9 u/ W6 S8 M5 G1 u( d. S# D) m求Leo与其他人的Cos值代码如下:
    $ }$ t* p4 a9 M; a* N, m* W( q5 y. E) g% ~7 k8 n9 R- d
    /*
    # c9 M' L$ \6 K  k- f1 ? * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来
    / Q! A, V! _6 N* F: T" j+ ]9 G+ B */
    6 k7 _- u1 ~  D' [4 o
    # n- o0 x! A( [# o+ ?! U+ j$cos = array();
    5 b7 z% D" R* i! z; s/ v; V& G8 K$cos[0] = 0;
    . D. ?+ G3 m$ l/ f  T5 X' F3 S. n! x$fm1 = 0;
    : q: ~1 Y9 L0 `) K3 f//开始计算cos" Z; T' }2 o% Q* ?) W
    //计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容+ Y/ U1 V: X4 `; @  n
    for($i=1;$i<9;$i++){8 s- V% ]5 ?1 H) o5 W& \, |
            if($array[5][$i] != null){//$array[5]代表Leo
    6 N9 r, k: x. \6 z: H, k! P. z0 r; |                $fm1 += $array[5][$i] * $array[5][$i];- E6 J4 `# r4 Y9 t2 m1 G+ V3 g
            }
    ' h4 w" R5 ]6 W) I  h}
    + |6 R; E: h  C( [2 J' Z' f5 V
    & B& m, m7 [5 a( L! S$fm1 = sqrt($fm1);+ B7 m# ^6 j$ E1 t4 V

    0 m, _! C# ]. zfor($i=0;$i<5;$i++){
    6 Q5 `$ c' s  u" a& F        $fz = 0;
    3 U. j# H% _; \% [5 [2 N1 ]6 q  H        $fm2 = 0;
    : L, x4 Z; s  ~; A6 `        echo "Cos(".$array[5][0].",".$array[$i][0].")=";
    ' v$ i6 M" u7 d0 d, ?; I0 d        9 b3 j! g9 z# d; j
            for($j=1;$j<9;$j++){6 U, Z4 C/ W% |9 a; C  G
                //计算分子- {! t& T& n/ \# A$ R$ [2 N
                    if($array[5][$j] != null && $array[$i][$j] != null){
    , l* x% C; @- j- U                        $fz += $array[5][$j] * $array[$i][$j];
    . ^% p* l9 Z. M                }
    ! @# e3 z9 N; _0 c! A: X1 }) u                //计算分母2" ]+ i9 B. ~) h. `! E
                    if($array[$i][$j] != null){
    ; V9 E' U7 v4 [& O$ V3 P                        $fm2 += $array[$i][$j] * $array[$i][$j];
    & c9 K, v  v% N/ d( s4 U) N# b5 a                }                        4 L% Q3 K& h9 e
            }. U3 r9 e8 S- M
            $fm2 = sqrt($fm2);4 E5 x4 h; O& K2 c1 s
            $cos[$i] = $fz/$fm1/$fm2;, p* j. M- c/ l. p+ C0 h
            echo $cos[$i]."<br/>";( j8 E8 Z% Q. Y  b4 `% t
    }
      T3 S2 S$ o1 ~% j8 y: J: J0 Q, N
    , u6 c0 R2 C" k/ ]' W! `( X这一步得到的结果是酱紫:
      X1 B. \/ _7 G1 ~; N2 Z' ?+ @$ `, }. |; I
    将求好的Cos值排序,采用快排代码如下(百度copy而来):
    : h/ b- c& Q! {/ F
    % {" K4 g. f9 B9 D( ]- ]- f) w% @9 Z. t( y
    //对计算结果进行排序,凑合用快排吧先
    3 i- K0 K0 U4 N0 K$ ^function quicksort($str){
    ( W) H6 k' i$ u! E' A1 L        if(count($str)<=1) return $str;//如果个数不大于一,直接返回4 s+ X6 G8 s7 s+ V. D8 `
            $key=$str[0];//取一个值,稍后用来比较;
    # i3 O, G8 M# ]( f        $left_arr=array();9 Z# c6 e4 _5 A7 F3 x5 C+ v" B. p: I
            $right_arr=array();9 m: u2 Y' Q5 Q, v  D
            * I, B+ ]* ^4 x" G+ D' I
            for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;6 R+ J* a' g& C$ r5 Y% ?4 V& |0 q% ?5 }
                    if($str[$i]>=$key)
    & E9 z, `, ]) p  p0 Q2 [) t% _                $left_arr[]=$str[$i];0 @: P( n6 J* f  D
                    else
    9 h* _! l' Z/ w$ L% d0 N6 Q                $right_arr[]=$str[$i];1 O6 i. B) t- v* {
            }
    0 a* l6 b& l  W, p; X( v" y        $left_arr=quicksort($left_arr);//进行递归;( N! n" ^: e! J2 K4 ?' R
            $right_arr=quicksort($right_arr);, n% z! ~+ ?: K0 P1 ?* S! ^9 Z
            return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
    . V2 E7 m6 S2 @  d1 ]}- W4 E: {0 T" [- ]
    9 b, w* m/ N3 _) o
    $neighbour = array();//$neighbour只是对cos值进行排序并存储/ U7 M- R. I) Z3 d
    $neighbour = quicksort($cos);2 d6 I: l# r, D# U
    - P- d& g& K4 l- z  d
    + S' e# T- P8 B
    这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
    $ B$ t' w# x  B0 D" H1 O3 j: x1 J, Z8 [: v: }
    选出Cos值最高的3个人,作为Leo的邻居:5 a: [/ s- B6 m# h

    + M, P8 i& h# {/ h: U! U//$neighbour_set 存储最近邻的人和cos值
    : n  m1 [/ `; ]) M- E. H$neighbour_set = array();* F1 l* h0 F$ h1 Q+ m7 J# q
    for($i=0;$i<3;$i++){& b' i% u. a$ X7 d# k( ^
            for($j=0;$j<5;$j++){, Q# C$ A- E" ?* l6 F. u
                    if($neighbour[$i] == $cos[$j]){+ o8 h! ]' L1 E2 v
                            $neighbour_set[$i][0] = $j;4 x3 a3 e4 H' M- P" Q
                            $neighbour_set[$i][1] = $cos[$j];+ N% J3 k; ]0 a4 N1 K! V
                            $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
    7 y' e5 S, t% y2 U2 l; m! ?9 w                        $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    ) h! ?. P0 ^9 ]9 d4 T                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分2 w, J" M0 ?9 q( {  O' z  R
                    }
    8 Z7 P1 |2 }+ x2 v0 X        }
    , b/ J1 x* _* i' O4 x}
      I; K7 x. G1 P. Q' i# _5 Xprint_r($neighbour_set);  e8 k& D* K" v3 A
    echo "<p><br/>";" \) s2 e0 ~$ u3 q, T. M
    : h: \4 o3 y6 ]8 A/ w
    这一步得到的结果是酱紫:$ {0 ?0 ^9 J1 t6 G* S

    3 c: O0 k. `; \, w* q) I  q# x) @/ ?/ D+ Y  L
    6 r: ]& s; |  o5 N( M* v
    转存失败重新上传取消$ m: J" T* Q: v. C& Q, J+ m) c
    ; G. b' t! m+ L- w1 [* e8 w
    这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。$ q: P: B! P% u8 X

    9 K* g1 ?/ w  G& T7 @/ k开始进行预测,计算Predict代码如下:
    9 E" c  b/ N) C& d$ ?, }4 h. ]/ M6 Z9 h3 z( Y, T. P
    我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。+ @( L% t1 C6 h4 Y" L6 i2 s

    2 m7 F0 }$ o7 `9 e, _7 F/ R//计算Leo对f的评分; ?! j5 L2 B8 y+ U7 U9 C
    $p_arr = array();" o. e2 u$ L/ z% Q
    $pfz_f = 0;
    * p/ B( o* g4 Z$pfm_f = 0;" U, H( d& ]" c! ?$ f' M5 n" J: c
    for($i=0;$i<3;$i++){, E( b, G& s! z4 n/ A* j6 G
            $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];# A. C# H2 ^" i! |/ Q4 J) C
            $pfm_f += $neighbour_set[$i][1];7 C5 \7 {9 b2 x4 j
    }
    ( T/ c4 T# s. @( Q, `' ?; ?$p_arr[0][0] = 6;
    . M' A2 b. ^" N' N8 Z$p_arr[0][1] = $pfz_f/sqrt($pfm_f);
    ) D& ^% `4 A) b7 [if($p_arr[0][1]>3){' g1 P, M! b  s' |% e$ X
            echo "推荐f";' E( a8 `; J: J; g9 U( i3 h6 I
    }
    + v* w. y& q3 y% C- K
    # K. g7 u" N7 a" o2 o7 K; H//计算Leo对g的评分6 k# f. \. l; T
    $pfz_g = 0;2 C0 t- L2 v4 y
    $pfm_g = 0;+ j% \6 e, a! `
    for($i=0;$i<3;$i++){
    1 }. @& {( |5 r        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
    ! n, y2 m- F2 W, O, ^        $pfm_g += $neighbour_set[$i][1];' M7 r: m& ^' N- l6 P! z  F
            $p_arr[1][0] = 7;6 p7 R& P% w' L; L: _! `  B5 ^
            $p_arr[1][1] = $pfz_g/sqrt($pfm_g);
    ; I( o4 D+ t. O}" o/ b5 J# W& m; z
    if($p_arr[0][1]>3){
    " I6 l9 @. s! I* D        echo "推荐g";
    0 F: v8 c0 P/ ~( g8 Y}
    5 e- ?# C/ f" _+ ^% }, P! v3 A! ]# g% l8 `; Y$ N1 h$ X: d
    //计算Leo对h的评分
    0 Y5 y1 q  I! `/ q; x$pfz_h = 0;
    # I, x: w% D2 t# K; k$pfm_h = 0;
    # ]7 d1 w: C) V+ r$ K- Cfor($i=0;$i<3;$i++){- g  \- G, {+ G  |0 N1 B& M
            $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
    3 W& m: x2 l8 [# h. L, @        $pfm_h += $neighbour_set[$i][1];
    3 q+ y8 z# x7 q$ T" a" W        $p_arr[2][0] = 8;
    + B- n+ E- A- `' p. i6 f4 v8 {        $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
    , N( O! j3 W7 ?}* O: x: v- z/ J6 b
    print_r($p_arr);# b1 a" N# f& @! z
    if($p_arr[0][1]>3){( k) s6 W/ G; ]
            echo "推荐h";
    5 v$ N4 s3 q- u+ f6 d% j! [}6 w, q9 i. [8 z* Z; W! [

    0 a4 }% b2 n0 Q3 [$p_arr是对Leo的推荐数组,其内容类似如下;
    7 ]3 W) P# {/ c! G- W1 _5 u/ P# k6 A7 q* A
    Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )+ U/ h5 ]- o& K2 v& f6 |
    ; ?9 b6 X/ Q& b' E/ R! {1 ?
    f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........
    " e7 I" N! z0 y  W
    1 ?4 c1 f+ }1 q求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。& X3 w' b# n8 z( Q
    3 l7 x  K$ Y1 U0 t! a' ?6 {
    从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
    5 Q# T! L, w1 p$ J% X6 _1 U1 V' I0 N9 b' X+ V. g6 E
    1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
    & M; {# i! A; F
    : z" d, `. q1 c  ]* l/ _1 L/ t" \7 U2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。# ~) [) C: |; }" _
    ( T+ [3 J) S9 e
    3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
    6 l2 _. C' M" ~7 j3 t7 g: v0 P  E1 n3 y% e3 I, K3 x) N# O0 K! D5 i
    4.可以适当引进基于内容的推荐,来完善推荐算法。
    : U2 G' l* K' {2 P; L1 L* E, w
    - n4 w& `6 j" Y* I8 _5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。# e. B$ b( d8 V: D3 M0 a
    ————————————————
    - g, c& V9 Z6 `: a4 k1 @5 f, C版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    8 {: t+ W* S& J; P* k9 z原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
    1 y0 K, z1 h: u1 U" }: @' D" X  @
    + g/ D  |. T- V( e, y. j8 T) p' ^
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-6-14 18:07 , Processed in 0.635371 second(s), 51 queries .

    回顶部