QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1925|回复: 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
    ( d+ J$ Z$ V# r
    php+mysql实现简单的协同过滤推荐算法
    % V5 _0 w) ]8 T8 D6 r; U仅做标记。。。3 S# ~1 p5 [( l$ ^7 t" n
    ' a) [- B9 A) M

    1 W. f; ]. U8 u+ s3 h( s$ ~
    # o# c% ]5 E4 a) B6 ]要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。7 |% T$ G. O5 z* n6 ^8 X
    5 i+ K7 L0 P& q6 T% S; P5 h& R5 x
    算法核心的公式如下:
    * {! V3 T8 u: b: \5 Q. m: w. d, z# D( j" j
    1.余弦相似度(求邻居):
    , G& V3 @4 j4 y/ D0 A3 Z) A6 @( E7 t* V: e! |
    2.预测公式(预测a可能会喜欢哪种物品):8 f6 s% T1 {! v1 \0 b, p9 p

    ' N' ?, g; a8 g' p6 Z# e仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。
    ' ]$ s) V/ [5 M2 r# Z8 t# v7 P; a6 j; ^7 S8 M" ]
    首先建表:
    5 h' a+ A% j: t0 W3 z
    8 M! B' S" y  E% q( Z9 C* i4 ?DROP TABLE IF EXISTS `tb_xttj`;
    8 m0 s; T6 S/ B! c! i/ ]CREATE TABLE `tb_xttj` (
    ; M5 J! A6 Z$ P0 Y/ k  `name` varchar(255) NOT NULL,
    3 I9 Y$ ^6 r' a1 M/ f) q  `a` int(255) default NULL,% A. g6 x1 R* l3 Z, @% h
      `b` int(255) default NULL,
    + {1 M" E: \% T6 _  `c` int(255) default NULL,
    ) Z# l0 t7 l% ~& ^- Y  `d` int(255) default NULL,: Y. H9 e/ Q& v1 C& _! A) @( K
      `e` int(255) default NULL,; `( t1 `; N$ ~4 }) c4 v% L
      `f` int(255) default NULL,2 n7 o- d: H, W$ K; ?! \
      `g` int(255) default NULL,
    ! O  M3 h3 k! G) }  `h` int(255) default NULL,
    ( Y6 t( [( U7 G. [, u' ]" R5 _  k  PRIMARY KEY  (`name`)
    ' V% ]9 h+ t% X- Q% s3 o3 ?) ENGINE=MyISAM DEFAULT CHARSET=latin1;
    $ M" C# O, Z2 n- s8 ^
    ! |6 k( _3 e! Q% X$ e/ tINSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
    & l& ~" ^  i8 e/ L6 N4 H; n; {INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);2 i6 A( D+ e$ ~  i' V3 i3 S- o
    INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');" Q! |1 O8 e) Y5 N
    INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');; N: p: U, {  V7 f
    INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');' ^- r$ Q1 I) k. r0 j6 F
    INSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
    / I/ l; B' q% ~# G, H4 O! m9 x- c4 T
    - n9 Z* B* P* j( _6 K# x
    我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。, s* r! ~* \: p, S
    ) M9 c6 @" G$ \' P7 M
        用php+mysql,流程图如下:& X4 t2 p/ T  h$ K2 {4 q7 q

    + Q1 B$ w: k: T# }0 U( y3 _, Q5 D( w. t连接数据库并将其存储为二维数组的代码如下:- @. h- z; t) Y* [9 o& Y
    % f/ s# O& G% m8 S. U9 h2 J
    header("Content-Type:text/html;charset=utf-8");; G! W; L% g  D  ~' u' R3 t/ W
    " p# Z8 h: I& M- N
    mysql_connect("localhost","root","admin");8 O) n$ g7 i6 K5 C4 e
    mysql_select_db("geodatabase");% |( j" n% V/ _1 {" }4 i
    mysql_query("set names 'utf8'");       
    ( p+ x$ Q( U5 o9 F8 T: @7 J8 v. M5 {! E
    $sql = "SELECT * FROM tb_xttj";( C6 H! G! ?( z( |9 }( Y. n/ I- U
    $result = mysql_query($sql);
    9 C$ C  D1 N9 t; Z/ x2 w
    * _) V4 F; D, D' g7 [. C# }* O; g$array = array();0 v3 @, f; L) I7 w. v0 B
    while($row=mysql_fetch_array($result))1 Z2 a0 @; V$ P
    {0 T; }' j! J' l* C' |: I" F# R
            $array[]=$row;//$array[][]是一个二维数组
    % m/ H2 S. r: v( {: T1 t} + K. h# i+ S" y

    7 R* k/ @* ?! ~! P# D问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
    ; {4 w4 q+ @: T, h& e. j8 T1 d& Y7 Q2 |2 V6 j
    求Leo与其他人的Cos值代码如下:& j+ `- W- J9 O$ H! i

    ' W4 k2 B: X7 d/*
    ' A/ {  r9 ]. k& y * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来2 {8 B& f- e' F, I( G, x
    */
    8 w' J4 W$ e; a% M+ R' z) l/ F+ w3 N( f/ e1 O# @1 Y9 Y" S! {3 h
    $cos = array();7 K5 s* D8 f. \# j! O5 v
    $cos[0] = 0;# ]! [9 z7 F4 n' h" w+ s
    $fm1 = 0;5 r+ J1 G: t( q7 p, s+ V' f
    //开始计算cos7 X  W1 ?) o- i$ g! @
    //计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容# @% x+ e- X8 Q/ O) w* C- q
    for($i=1;$i<9;$i++){# _8 y( {+ ^9 a: j9 b( x* G5 `5 J
            if($array[5][$i] != null){//$array[5]代表Leo, q& y- L2 W+ Z' s+ v1 q  \
                    $fm1 += $array[5][$i] * $array[5][$i];+ D7 i3 l6 A' v8 Y
            }, ]* a3 q9 q$ w# [+ a- ?/ V
    }
    5 ~. H. W  O+ h2 Y( S1 A
    8 }" g: j) r6 s# P- b7 s$fm1 = sqrt($fm1);" ]2 `  O. ]# ~: Q- T. I
    0 j- J8 G* w$ T
    for($i=0;$i<5;$i++){" i6 f. b9 S6 u) f. @7 \( v
            $fz = 0;
    ( q, v0 E7 t3 Q' h7 k* p6 a        $fm2 = 0;# @/ d* \' |( v8 }! Y0 p. `
            echo "Cos(".$array[5][0].",".$array[$i][0].")=";
    8 L' t. m% j& L4 Z0 n' ~: o% Z        - r: e% O$ `, S: |. s. l/ f4 j- ^5 C
            for($j=1;$j<9;$j++){. ]1 \) s! [! Z) N% [' j3 C8 R; A
                //计算分子
    9 U8 l7 y" |0 G; f( ~                if($array[5][$j] != null && $array[$i][$j] != null){2 C6 G) Z9 H3 D8 S0 a/ I
                            $fz += $array[5][$j] * $array[$i][$j];
    % x0 [1 z! ~( b. k+ m  C% x( L                }  A; {: f# b2 F" E: }. U1 M# t
                    //计算分母2* ~  m/ k7 Y/ m
                    if($array[$i][$j] != null){# p. b, b( \- [0 [
                            $fm2 += $array[$i][$j] * $array[$i][$j];- ]* T9 v! e8 |% v
                    }                          d/ w% W- ]& r' G4 |: s
            }3 M( I$ R1 y5 c! J1 V3 x6 z* l
            $fm2 = sqrt($fm2);) t! y+ Y6 d  [5 v0 R* Y
            $cos[$i] = $fz/$fm1/$fm2;7 a* ?! H/ N' F- L
            echo $cos[$i]."<br/>";! B, D$ U- X4 e+ S) F7 Y- s
    }
    4 k, O! i% Y1 ]4 A( X$ z- b8 O! a3 o7 B" m5 o: D, Q
    这一步得到的结果是酱紫:
    ( ?# y. \  k0 P0 S6 q0 y
    ! d" @' U; G$ T- _; `将求好的Cos值排序,采用快排代码如下(百度copy而来):
    * b- R2 f3 L7 I4 T* t- a* O. s1 a
    5 O% c) A0 g; U4 P* e, c/ ~' c7 n+ ]) G0 p
    //对计算结果进行排序,凑合用快排吧先
    , e9 ^, i8 M7 Z7 J, Wfunction quicksort($str){; F2 L  k3 |- c( ^& y
            if(count($str)<=1) return $str;//如果个数不大于一,直接返回5 Z8 J1 T! \6 _; u; R6 M
            $key=$str[0];//取一个值,稍后用来比较;; u  h; X# b9 e& O, G7 f9 u/ q
            $left_arr=array();
    6 N% J) C5 W; Q  x- l  v        $right_arr=array();! t9 I+ n- ~2 D9 ~8 v" y
            $ N8 T  J- S: f
            for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;! ]6 N- X/ a) k, X) \, w" x1 D2 P
                    if($str[$i]>=$key)
    : M, v% N( X* R+ ?7 ~                $left_arr[]=$str[$i];0 |3 N% f2 w$ A  n" J
                    else
    , ]; N' S& H$ J9 |" M1 D! @. v$ R                $right_arr[]=$str[$i];
    4 @, q( \3 U  L% I# ?        }
    7 A! q% ]% Q' E9 ~        $left_arr=quicksort($left_arr);//进行递归;
    $ R, E7 V( d6 S- a, B2 q        $right_arr=quicksort($right_arr);/ _: N# _4 H2 D: J" a. R6 J5 \
            return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
    8 a7 d# }8 t" f8 X/ D}
      x% ?  b* k* N
    ; c1 G" _$ ?  I) H4 K$neighbour = array();//$neighbour只是对cos值进行排序并存储
    $ Y% H% K6 v  ^$ H8 u1 F2 A! K: Z$ l% @$neighbour = quicksort($cos);
    & R1 ]8 r! I$ b* P0 c+ t$ G7 q2 c6 _2 i4 M# n) \
    " z5 c5 O# j' e* B& K) Q. M7 n1 V
    这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。7 p- f( C' h* Y1 C7 j" V& s9 T, R

    9 {2 A/ |+ H* J4 o: X6 A5 Z选出Cos值最高的3个人,作为Leo的邻居:
    ' @' l! f2 w$ h* y3 s
    ) |( T( |/ c+ Z8 b- S* j//$neighbour_set 存储最近邻的人和cos值
    $ U% }4 r/ w* A+ w# q2 d$neighbour_set = array();3 d3 Q6 ~7 L0 ~9 Y
    for($i=0;$i<3;$i++){) T$ x% @( F! B! M$ e1 r
            for($j=0;$j<5;$j++){& U0 y: T' r8 G1 `  Y5 R+ w
                    if($neighbour[$i] == $cos[$j]){) {8 s8 |$ L6 x0 Y
                            $neighbour_set[$i][0] = $j;! L) }! _& L  W) @; u& k
                            $neighbour_set[$i][1] = $cos[$j];$ A% q6 W! U8 a
                            $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
    5 O# ?7 l2 |3 `, s1 h8 {5 m, d                        $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    * ]# l3 z' z- N' ?9 W( @3 P                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
    - f  Q7 z# U9 _! o                }
    - k- u: H9 d0 L        }
    % x1 K8 S! Z) E}; N2 \( R( J% ]
    print_r($neighbour_set);
      e7 L! z) F& Z* z/ M7 Cecho "<p><br/>";; [4 Q* r, l6 \6 X' c3 _( O$ s! i

    $ @, q% B0 Z4 C2 O) \6 z7 s" e5 ?- `这一步得到的结果是酱紫:
    / v' ]" X" u1 I9 w) c# {$ D- j7 h1 h2 `

    $ s4 c: W$ C. B% o5 j% r: ?+ Y+ v" ^  i+ I! F
    转存失败重新上传取消
    " W4 O7 Y* M1 R9 i5 L6 L- q- `' N. Z- h5 T
    这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。
    & A* f* c$ n* {; K
    : g: o4 f( e9 S2 w开始进行预测,计算Predict代码如下:1 B$ s4 Y* x) L2 V+ N- d

    2 `+ C% S4 Q0 L( G9 q$ G我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
    0 A$ B3 T$ X7 ^# O* ?5 B' Q* t: O' h* b5 d3 o
    //计算Leo对f的评分$ ]5 }; n9 `0 J
    $p_arr = array();
    ) s0 D2 w9 ?, \1 e6 S$pfz_f = 0;9 ?: V$ v$ w8 S9 L, B1 n9 @9 b% G1 t8 P
    $pfm_f = 0;  }* Y, _/ ~; l/ u
    for($i=0;$i<3;$i++){
    $ e9 J0 S; n* s) q) p% O% L& i        $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];1 T  p* C8 ~: W2 S- n4 N: |
            $pfm_f += $neighbour_set[$i][1];$ t6 P4 K. O& O, w( j) Z# q
    }
    6 Z' L$ I3 U% |$p_arr[0][0] = 6;
    7 L; V2 p! C" W# n7 e% n& ]/ J$p_arr[0][1] = $pfz_f/sqrt($pfm_f);  w. I4 U/ P+ t- r( y% H2 X" Z
    if($p_arr[0][1]>3){
    % S8 Q6 }9 r% V$ m        echo "推荐f";
    5 g: T& [4 F' S- G4 d$ T7 N}% U' v- {: R4 Q! e( \+ ^- Y

    ; I0 z4 N" y! s: A$ ^* \+ n* c1 f//计算Leo对g的评分
    : `$ p2 Y' h* q# y' b+ k$pfz_g = 0;
    % Q. |% s$ f& u7 s7 Q+ A: b$pfm_g = 0;
    7 Q6 Z6 W7 `8 ~. l9 Ofor($i=0;$i<3;$i++){
    9 w! f9 o/ H7 K7 ^        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
    8 t/ Q( w/ o6 g$ c        $pfm_g += $neighbour_set[$i][1];7 V# Q7 a" |: ~8 z: E
            $p_arr[1][0] = 7;
    * B5 P8 C' r, L3 e        $p_arr[1][1] = $pfz_g/sqrt($pfm_g);
    2 |4 c9 B! b" X5 Y0 _}
    $ L* A7 L* \" ?; qif($p_arr[0][1]>3){
    4 k5 S. z8 I/ [9 A5 O9 S7 {        echo "推荐g";( ?7 ~' d+ i( p5 l6 }
    }/ `8 x/ a4 h5 O2 o& r

    ; T+ e- }* }- @" k: \; D//计算Leo对h的评分$ U8 f) D  w9 A8 r" `; @
    $pfz_h = 0;
    9 ?, J8 [$ a) M1 ]( d% ~9 K$pfm_h = 0;$ f% O# E% h* v, f5 {& F5 z) Q3 w
    for($i=0;$i<3;$i++){
    * I% M1 p& ~, c! z1 x        $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
    , M9 W* @( J) ]! W9 U3 s8 `4 P1 x) M        $pfm_h += $neighbour_set[$i][1];
    7 t! `# n+ F- J2 y! {        $p_arr[2][0] = 8;1 V9 B9 o9 r3 G/ h* |
            $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
    3 A; o7 @8 `1 m9 Q: f$ }+ @}: P& v6 G; B9 f9 ^
    print_r($p_arr);% [6 k8 x3 F" t
    if($p_arr[0][1]>3){  m& c7 i. l: \9 D# g
            echo "推荐h";8 m; e* y% O  D  s" p
    }8 U3 L# g% R! i" C

      B/ K2 {; _  [9 D# ~$p_arr是对Leo的推荐数组,其内容类似如下;) k! a* S& r5 R$ c: p0 @
    6 U, b9 f7 W( S% [$ N" v+ j; k
    Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )
    1 F; U" e4 t$ z# p& D' D$ Z7 D, E# v9 e. _
    f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........8 o% K0 E: I8 ]& d1 t; s/ V
    7 S. P, v0 M( m2 j% S( ?
    求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。5 g/ a. G4 n$ x0 Q6 e7 k

    ! ^* B. g2 R1 b- P  H* V( r- |! J从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
    8 T0 Y1 O0 l, e" `: N6 f; F6 b6 h( L9 a+ G0 u2 i6 ^0 P2 j
    1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
    + X: y' f- J9 U, y# o; k1 ^1 D$ ]. C; [" n4 q8 n
    2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。
    % d8 h; s( s) U2 }, I. M( W) D7 @1 m" U! l; m1 K( x! l4 Z1 y' C
    3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
    # ^' ^* I; a* e( S, y! O! F3 z: D% F' H9 O! ]6 |+ j
    4.可以适当引进基于内容的推荐,来完善推荐算法。
    + f3 l) R" w/ k  |& h( I, P7 Q$ S. \5 e- v$ f
    5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。
    & q; M; H! `) Y& |& j————————————————  u$ ?" [; g* W6 Z2 u$ p7 v3 }
    版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    4 g& _) L6 n7 ?) r% v* B, i" |; H原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465  y* j0 Q% d4 c6 t) h+ Q

    4 q/ ]' |& g9 d) p6 u6 D
      }3 s+ @- b7 ~% c; `' Y
    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 21:30 , Processed in 0.343846 second(s), 50 queries .

    回顶部