QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1922|回复: 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

    $ Y& Q) V: R4 T' O" q' jphp+mysql实现简单的协同过滤推荐算法! i9 o# d4 P8 t. r% S
    仅做标记。。。
    2 |! P& h" g5 r! D0 K. l
    $ J# p! o! Q; C0 o. G- h5 r; r+ G- F! V

    % `3 X/ t" m3 ]7 D# t; V要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。) P5 H3 W% X, W: T
    ; v1 A& i& S, J' Y
    算法核心的公式如下:9 P5 p% y4 ?3 o5 S

    & ^8 V. D2 U9 N6 U  N, j& I/ I1.余弦相似度(求邻居):
    * u& _$ R  `0 c7 ~; Z4 S' A& c" s  m0 O- B1 U* j7 g
    2.预测公式(预测a可能会喜欢哪种物品):8 w, @( Z- ?& i" D. B

    * b0 u, a* ~& S) V% Y. k) |仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。
    . q! `5 w. o; ?1 ]$ L7 ~1 e: t4 W8 ~
    首先建表:
    2 o" F+ r0 b, q% b
    % A9 p* p! N" `: x3 TDROP TABLE IF EXISTS `tb_xttj`;
    : J3 d! \  t0 \! L& N; ]# xCREATE TABLE `tb_xttj` (
    6 s$ f; g7 m# r2 e  `name` varchar(255) NOT NULL,
    4 O" q2 b2 b" `: `; N4 ^! }  `a` int(255) default NULL,
    " z! f: ^% Z3 e0 l+ m  `b` int(255) default NULL,
      N4 w; R- J7 B  `c` int(255) default NULL,
    2 _$ ~8 T; N& w- m# z; i8 y' R  `d` int(255) default NULL,
    8 H8 c5 |# ~0 M  `e` int(255) default NULL,: k, |; J6 S" b6 q
      `f` int(255) default NULL,
    ! F7 |7 w; j( D/ S  `g` int(255) default NULL,
    & {8 x; J+ f7 J8 }8 l4 [2 R  `h` int(255) default NULL,: b+ [  Y; H2 q+ |' o( s7 J& P
      PRIMARY KEY  (`name`)% W# y8 W2 [5 l% x/ `
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1;2 ^: x; X" [: G  W" C' ~; r
    1 ^& A3 q# G7 L7 U# J) Q3 @, @
    INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
      y& i: \2 m6 o, S: A) bINSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);
    : m/ U; J3 {6 N3 S# Q+ wINSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
    8 l. M6 f4 E  B4 ^! A) F! p8 bINSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');( a- x; x5 Y8 _) [% Z7 u1 D0 Z
    INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
    + W; t& F% p/ v; ~- zINSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);& V6 l/ O% _, W; `1 L  m3 l

    8 X' e6 ?2 w6 c
    ( z  m. y0 r! c/ j  w7 _% m' R  s 我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。+ \6 ~) T" ~' U; \+ f) g* Y  P
    8 u+ e: V; e+ a- \; v2 x
        用php+mysql,流程图如下:: _  `9 i/ x% t' i- b; p1 [9 a
    . q' c! d+ O- y
    连接数据库并将其存储为二维数组的代码如下:
    2 C# K$ J) A. [7 h8 Y) g8 C3 t( L1 |: R
    header("Content-Type:text/html;charset=utf-8");, T6 K3 g) |- r1 ^8 }  y
    4 j5 v$ q8 s8 _) ~
    mysql_connect("localhost","root","admin");  H  h; {, \" u* I3 m# Y5 O
    mysql_select_db("geodatabase");
    ' o, l: p/ c7 k6 pmysql_query("set names 'utf8'");       
    4 h  x( y$ I7 `' f* g( U1 u: s
    + i3 S# |1 d6 ?# o- J$sql = "SELECT * FROM tb_xttj";
    9 }4 ]. S7 Y& k# M. e$result = mysql_query($sql);
    3 `" \' w5 u) _0 [8 |- N5 b3 d0 c) f5 V# y
    $array = array();
    * h5 x( D* ~0 w% g  @while($row=mysql_fetch_array($result))8 R6 B$ O( h; y% O" `- J
    {( W" @- q2 e# `/ G
            $array[]=$row;//$array[][]是一个二维数组3 F( \$ J8 Z: [. n8 H: [
    }
    9 f: l2 c* T/ t. R1 m/ Q' l3 p" H8 V
    问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
    # Y! o0 t" x0 z2 Z' M+ \  n+ E2 z/ s" H
    求Leo与其他人的Cos值代码如下:
    / U$ d' C  N( n$ J1 C; v7 N# _# H6 h
      ?/ J% Y0 F+ v+ f1 `/*
    ! s' R( O' q) Q/ F" x+ X * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来
    & u0 t4 x% o" c */
    7 V) s9 d: M7 e
    * y1 g" }: m& q8 F# Q8 q. G$cos = array();
    * y; W! G2 o) x( \% i" W6 f  C$cos[0] = 0;
    3 x# H, [3 S9 `- t& X4 P9 |$fm1 = 0;
    % e+ Z$ `0 b+ o- ^1 \& l$ `" G//开始计算cos
    ( B3 G" |; \" G' v# l9 |' i//计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容
    # M( C* W$ i; O! Z3 O( }, u9 x9 L8 ffor($i=1;$i<9;$i++){% w- D5 m4 q& ^# ~. p3 B' A0 X/ R
            if($array[5][$i] != null){//$array[5]代表Leo) q( @- D+ s# |! c
                    $fm1 += $array[5][$i] * $array[5][$i];8 G9 }* O4 |( V# a9 b2 N3 B' K
            }4 r$ w) K. f  y. w
    }
    , j8 Z6 }( N# r9 s4 g& E
    7 x6 s" K; ^% U6 j2 l0 H% e" U$fm1 = sqrt($fm1);
    - i2 I% \; r7 y' i/ v# ^( s$ S9 k$ \$ u* b* [" f/ I
    for($i=0;$i<5;$i++){; q# U7 E% B+ r. K+ v
            $fz = 0;
    1 G, i7 C' H5 b- ?+ y        $fm2 = 0;
    0 P) c1 B5 B# J. [1 I        echo "Cos(".$array[5][0].",".$array[$i][0].")=";
    & ~+ {) w- w; v% G        & _8 }% D; ]; [( f$ I
            for($j=1;$j<9;$j++){8 ?& Y. u. Q8 g, t# L9 w+ r' b
                //计算分子- [0 ?- _  R$ A) U
                    if($array[5][$j] != null && $array[$i][$j] != null){! E' I% j1 x5 M- D
                            $fz += $array[5][$j] * $array[$i][$j];+ z3 ]) y! D$ H& w" ?
                    }
    + {6 _: t3 y- n0 \                //计算分母2
    # @  l  |6 R  Y                if($array[$i][$j] != null){8 [) s. y; f2 H6 G; W# F+ [3 E( C+ ]
                            $fm2 += $array[$i][$j] * $array[$i][$j];
    $ ]8 @9 X& B3 E7 x, N2 _6 _                }                       
      `! t+ B; ^& a( ?$ l: B        }
    9 Y4 [' g, P6 V        $fm2 = sqrt($fm2);% A% `' J  Y0 z; q
            $cos[$i] = $fz/$fm1/$fm2;* D% ^8 z: H' S( R9 C
            echo $cos[$i]."<br/>";* d* Y4 y' ~3 K* r
    }
    / d; c5 X% W6 }( F$ k! F1 }! i& {# n& ~+ R, u; X6 d
    这一步得到的结果是酱紫:, Z% B) Z* X' _# X$ T* S

    & Z5 w) e1 E. w7 g6 L: O+ t将求好的Cos值排序,采用快排代码如下(百度copy而来):
    : ~6 M9 _! \8 {3 V- z% u9 I& K
    9 n8 R7 F  Q5 ]1 c2 k
    9 {6 k2 c/ _  n* e) c5 x//对计算结果进行排序,凑合用快排吧先
    - l: E" o" o2 @: j" ~8 @& D. nfunction quicksort($str){
    ' Z/ l! g% t/ W& A        if(count($str)<=1) return $str;//如果个数不大于一,直接返回
    / r" ^9 d0 Y  w, i5 N        $key=$str[0];//取一个值,稍后用来比较;
    " Y+ {% c1 @2 Q/ x, I        $left_arr=array();! W9 M4 p$ t! K
            $right_arr=array();
    7 s* k2 \  H& v6 ^        $ Z9 Y8 ?# \1 t4 h9 s
            for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;
    1 R5 ]7 a1 c; h; N0 H' K! p  Q                if($str[$i]>=$key)! C4 j! n9 o8 \. c
                    $left_arr[]=$str[$i];
    . ]9 i7 w  B1 n. R9 x. R                else
    , k! z& H4 g( L% ~! Y  X                $right_arr[]=$str[$i];
    6 D2 O  _4 ?. p6 W        }0 |+ C  O* K0 |9 s0 O8 t" i% D
            $left_arr=quicksort($left_arr);//进行递归;
    9 {  _4 V5 K- S) I! x$ U9 o        $right_arr=quicksort($right_arr);8 c; D! O( V  o" |1 P% ~
            return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;1 k: M6 x9 V9 l' J) P/ M0 k5 x
    }
    3 k. v& B2 T3 P0 s, h* m$ p& E; B# D" V" r
    $neighbour = array();//$neighbour只是对cos值进行排序并存储
    5 k9 s: g) B- b8 L$neighbour = quicksort($cos);0 h4 L& R# Z$ x) h) Q: N

    $ f* }+ ^; H) h6 }* u, P
    % z. q- c1 X( }/ X这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
    , G* ]/ a( ]( U; k! z0 x9 I, a% u0 A3 V
    选出Cos值最高的3个人,作为Leo的邻居:
    + l0 \% w( e" d( m, l
    7 |) W2 R8 g& ~7 ?5 Z5 A) B//$neighbour_set 存储最近邻的人和cos值* }" h8 R- g( H8 A4 v8 l3 [/ y  a. [
    $neighbour_set = array();
    4 J1 u- X3 s7 P  p' w" cfor($i=0;$i<3;$i++){  ~8 x; z9 d% c/ }; e; H4 t3 i
            for($j=0;$j<5;$j++){
    , k+ Z1 n5 p# e! u- C* {9 Y( p4 I                if($neighbour[$i] == $cos[$j]){* ~! p* h1 g7 G
                            $neighbour_set[$i][0] = $j;. q- l4 m. T! v- Z1 T% ?) u
                            $neighbour_set[$i][1] = $cos[$j];/ {3 b3 a3 w8 |
                            $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分2 [0 b# A9 g, a" `$ W! k
                            $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    4 v" I1 m& {$ P6 x) b                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
    3 T2 D; T2 y6 b6 j                }
    8 u- f6 {8 v  V( t        }" P. d3 c. E. K% h
    }# g, u+ [) j! Z1 l; g) J
    print_r($neighbour_set);6 a1 h. ^8 Q6 G& a$ p, u
    echo "<p><br/>";
    % v7 p6 _; S1 V$ w
    . N8 ]* V) Y/ i! N- O' J这一步得到的结果是酱紫:2 z3 O) T# V: M% h1 j+ N
    ; H4 E. _1 k5 |+ Q

    3 g. }5 |# G' q6 [, N& w; Q$ G! `# Q
    转存失败重新上传取消
      N+ M! e$ C; W6 \* S" b" K$ H7 T2 d4 Y1 Y% |  J- a
    这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。
    4 L6 U) X% `) w# H4 t! z) q/ m8 C1 k, v' V5 T9 h0 u0 ~" m
    开始进行预测,计算Predict代码如下:% S7 l9 P6 O: I4 P

    4 y# @4 H1 [3 {3 o# {! c* U我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
    4 b  G* p9 p8 q6 {& L7 ]; {% c4 g( S: R; @( C2 s
    //计算Leo对f的评分
    8 o% f7 B+ H2 H7 b1 q4 D+ N/ p$p_arr = array();
    7 t- A. }4 C( e/ F8 d% }$pfz_f = 0;
    % `2 X4 V' O$ u. [( Q5 t% x$pfm_f = 0;; K( w7 ]( F; m2 q3 W1 J, x+ V
    for($i=0;$i<3;$i++){
    9 r+ `( |. }& @( L- I3 r5 H        $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
    6 E) g0 P+ x7 @0 [  q3 ^, V& A        $pfm_f += $neighbour_set[$i][1];9 J8 j, {% Y3 Q8 m, v( a
    }
    5 T' I1 S, ?" L  m2 P! [1 Y$p_arr[0][0] = 6;  Q1 C8 S  L5 q
    $p_arr[0][1] = $pfz_f/sqrt($pfm_f);
    5 ?9 F. e! ]5 v) A9 B3 e" X8 V  Tif($p_arr[0][1]>3){" R) ~/ E) T8 G6 S* q
            echo "推荐f";( R1 q- z% r) d( R
    }
    * D+ s0 b: L2 s6 e9 p0 p( g8 c- e2 i' K* y6 Z5 u1 }
    //计算Leo对g的评分. R# E* v+ N+ t. A
    $pfz_g = 0;
    6 w9 I7 X  W* V! Y$pfm_g = 0;
    - e9 M+ M1 |6 d+ h- p1 C2 Gfor($i=0;$i<3;$i++){
    9 F. ]! m7 j, ?5 u9 N0 q7 K        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];  r8 o% D$ {4 C* x
            $pfm_g += $neighbour_set[$i][1];
    7 W4 P9 H  P4 O2 t        $p_arr[1][0] = 7;$ `; z% ]4 `4 R2 Q
            $p_arr[1][1] = $pfz_g/sqrt($pfm_g);
    ) i! F$ ~8 m9 d3 Z}
    % K( `# E, Z5 J* W2 R2 a; mif($p_arr[0][1]>3){6 c5 c: ?" u( j! u* T# Z" k
            echo "推荐g";
    " r; N/ B6 i" M% i  L( B}
    0 h1 l6 m# u7 e1 ?
    , X& q& P( e( c" x! o( _! x5 d//计算Leo对h的评分
    / m0 X7 P/ t6 ?# o2 Q$pfz_h = 0;
    + T3 t8 t! i0 [$pfm_h = 0;
    , K0 J: {2 F9 O# _for($i=0;$i<3;$i++){
      k( L. I. T$ O; \/ K7 {" |        $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
    6 @% ^- z, K' Q- U/ L' D, d8 \        $pfm_h += $neighbour_set[$i][1];
    / V3 o' }8 C1 G+ c' y* P2 m        $p_arr[2][0] = 8;2 w  w7 n% P5 Y5 @( V- ]/ [% H
            $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
    ( U, E  M  ~* Q}  K! S3 I/ y% y! M$ C5 n
    print_r($p_arr);$ E$ T) k' a0 `6 L
    if($p_arr[0][1]>3){
    " |' q: ^/ ~0 P- o, A1 R        echo "推荐h";0 E3 f: q3 _- f2 z( L  P8 ~9 @2 e
    }5 _+ s$ G; b' J6 |$ }% [

    , Z, J. c( ~2 `) `6 V9 E$p_arr是对Leo的推荐数组,其内容类似如下;
    : i. ?, r7 i" k% a% I3 }$ ~$ t' ?3 Z( N
    Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )
    4 c% [5 o3 a4 h' ~
    ( H, q5 A, s7 g; g  e2 x* `f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........! E7 G6 v  H: f8 B
    + ?9 |0 V& n% p* P. h7 L8 R; m
    求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。; g, v9 e) w4 y& i, k& g

    / a3 s3 A6 y" x7 D) z从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:! |6 P2 _9 u+ }/ y- k

    ( ^& @2 n1 v5 p9 W% t5 c" O( [1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
    & H" c" B0 ^: }2 F2 b' w
    0 p  H! g" U& H" h1 S, V6 i/ M2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。: C8 K6 r. I+ J  C, `
    ) L1 m" |* |) y  i0 c+ o
    3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
    ! R6 U8 w' f* K8 U4 Y8 x
    " U" _' q- C: Q3 ^  C/ ]/ ?4.可以适当引进基于内容的推荐,来完善推荐算法。0 M3 H: Y6 k; l+ C

    7 `( O* Q2 R4 I8 V5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。
    - q* f* P) Z4 F: @" O" y; ^————————————————! N  L4 W% Q9 L7 `4 R
    版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 P, a& W+ l' ]6 S, X
    原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
    2 {( ~/ ^' }; g2 |8 g+ b
    9 H8 R; ]) ^4 q% h) x8 v  W0 T/ v  {" T6 B, n7 |$ K
    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-15 06:04 , Processed in 0.424216 second(s), 51 queries .

    回顶部