QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1896|回复: 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
    5 B* {/ f9 ]; q! w' m+ {
    php+mysql实现简单的协同过滤推荐算法. \1 g! W7 X  u+ o2 i  j6 ]8 f
    仅做标记。。。
    . n7 t+ T9 g: n2 _/ ?2 N6 ?6 }5 ]7 B' \* t

    3 ~. M! k6 I: x3 V' ~  ?/ s, k
    2 w8 w" K& v* o要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。/ b" I- S: Q1 @
    4 b0 i3 C* _% W2 W
    算法核心的公式如下:8 f" E1 W  k7 ~
    : a! V$ h/ m1 U  C6 `
    1.余弦相似度(求邻居):
    7 e5 r  {2 A7 X6 }# }; M
      P; |/ ]% r/ w' Q8 A/ u0 I5 L2.预测公式(预测a可能会喜欢哪种物品):
    0 }; Q- p3 a7 ?% {! f0 G: y! i6 V" P2 m, U
    仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。1 F- t2 [  b. d, N. C2 a2 a

    ( M7 u) p$ ^. \* A6 J8 a首先建表:% G6 |: \2 V, G; ~! }' r

    ; }4 E4 M: O) V: aDROP TABLE IF EXISTS `tb_xttj`;" e. H$ ?* i9 A8 o
    CREATE TABLE `tb_xttj` (
    ( C. c# i9 X- ~2 S  `name` varchar(255) NOT NULL,
    : n+ L5 d; h  `$ Q* Y( W  `a` int(255) default NULL,/ f' Y2 K( Q; Q8 b7 e
      `b` int(255) default NULL,
    " r  t) W4 ]- j  `c` int(255) default NULL,+ U7 D5 x" _& l/ O9 l2 U
      `d` int(255) default NULL,1 @" s# z" p2 W& k/ O" D& q: d
      `e` int(255) default NULL,
    , P8 l& J5 `) I; A  f  `f` int(255) default NULL,
    0 ]# u3 {1 V) W% r9 |$ Z. O( [4 A2 g  `g` int(255) default NULL,4 `* N9 z% z; t! M* @3 `
      `h` int(255) default NULL,
    2 U8 F) `: V: |* g  P  PRIMARY KEY  (`name`)
    . w: y) ^& M/ U# |) x4 S) [' j) ENGINE=MyISAM DEFAULT CHARSET=latin1;$ q9 Z6 u; K4 A- L. V8 w+ X

    % V' ^6 u3 r4 g+ w  h; f1 R3 ^INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
    6 k7 `7 e3 C* {' Y* y3 I4 h. s: YINSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);
    3 h! a' p" O( `9 y) ?INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
    ; ]' `7 Y6 L: `* i; a6 |INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');" e2 |1 f& F& K* G# M  @
    INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
    6 k' ?, p: N' O8 f# HINSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
    ' p. Z( c* [7 j' [- N) u+ B
    1 N7 G6 H& l7 H+ E! I+ E; j! \
    + S& _, ?$ o/ V2 O: n. e 我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。! I8 j- g& o  U  \, W4 O
    - g3 J3 d+ K6 e6 P9 }
        用php+mysql,流程图如下:8 c% X4 o8 s  g! i0 h
      V( e6 N- @5 e( {1 R: p  Z
    连接数据库并将其存储为二维数组的代码如下:
    - N! w8 w4 `% V7 O
    4 o6 m- C3 ^* @header("Content-Type:text/html;charset=utf-8");
    * r4 p. I8 R- H$ i
    2 t2 [" y# x4 \  L1 O1 Omysql_connect("localhost","root","admin");
    % ^5 G" G9 k  W# ^& k5 Y) a2 Wmysql_select_db("geodatabase");  A1 G* T1 ]; U; Q2 r: Q. J
    mysql_query("set names 'utf8'");        : ^0 _4 i  U. Z0 }

    ! {; X4 e& h/ R: q- K- F$sql = "SELECT * FROM tb_xttj";
    * g( v3 ?# p* H6 X3 i$result = mysql_query($sql);3 X5 I7 c" O+ T
    9 J9 u$ C# ~* |
    $array = array();
    ( t( W: B1 p, R8 Rwhile($row=mysql_fetch_array($result))
    ) g/ h8 m7 D. M' O3 G{
    ' H" \! D5 P1 C& G/ Q        $array[]=$row;//$array[][]是一个二维数组; X4 w. f6 Y$ F* [
    } ; ^: n/ s" w/ y, W6 L- P
    4 j% @* g: i" p( j, S5 g
    问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
    - ^4 m7 B/ ]  ?' J3 K+ `* n+ D  o$ P7 g  J6 q, ?- {
    求Leo与其他人的Cos值代码如下:
    / s) _0 u/ _' R' p9 W* p, P, x  ]/ R2 k0 x
    /*5 w. S. ]7 g  r/ P( d% u
    * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来3 r' e3 V& H; C! G. A8 c% o
    */# x; b: Q4 f! v- g: e

      I3 }) _4 J; ^$ r/ `. |! ?, ^& r+ C$cos = array();2 {4 T4 N3 L# {( ]" b
    $cos[0] = 0;
    ) {* Q1 V, Y, P$ _# f9 @; \9 F! b$fm1 = 0;
    7 v& i! ?: J! b: ^//开始计算cos) O% E, L. A7 [
    //计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容
    " [6 m# l1 Y; D% ?* a3 P2 kfor($i=1;$i<9;$i++){, R% ~" w. X& P  ^
            if($array[5][$i] != null){//$array[5]代表Leo, I) f4 _/ l  u* i
                    $fm1 += $array[5][$i] * $array[5][$i];
    * C# `! ~! c( ]3 e# k% ?        }! g3 L$ V7 \. V) B2 p3 M6 L( o1 S
    }) s: E0 r, A. ]( ?5 f$ v4 y* n  ^
    ; N/ d. S/ X( h( L. G
    $fm1 = sqrt($fm1);
    * y( [2 ~& v4 d% d9 ]* l
    ; @2 g  D" k$ d/ Z+ ]  [4 W) Vfor($i=0;$i<5;$i++){
    * w4 j/ m4 b0 B  U/ j        $fz = 0;4 d2 Z+ W  z( X. p7 [
            $fm2 = 0;6 ~* U' D& e; u0 H  T+ z
            echo "Cos(".$array[5][0].",".$array[$i][0].")=";
    5 N* b) w; u2 n. {; n3 h9 w        . \; z' h5 a( l% y* G8 G: y
            for($j=1;$j<9;$j++){5 d' Z) m' E- q
                //计算分子
    9 y/ [5 b0 n# O& h9 i                if($array[5][$j] != null && $array[$i][$j] != null){% ]+ c' J" D! L0 `7 Q5 }: @
                            $fz += $array[5][$j] * $array[$i][$j];
    ! Q3 H5 J- X- G* L) w9 }1 U# P! @1 @                }
    : P: |) f  ?+ S# y                //计算分母2
    : y( ~" s+ K) ^' U                if($array[$i][$j] != null){
    . q, n8 a0 }3 J3 ~: |                        $fm2 += $array[$i][$j] * $array[$i][$j];
    1 s: c* Q3 E; H                }                        & i8 i9 P( c% D5 W1 ]0 a) ?- d
            }3 _; i4 p( B2 J/ m
            $fm2 = sqrt($fm2);
    % @3 ?7 v2 B: Z0 A3 k+ m' Q        $cos[$i] = $fz/$fm1/$fm2;% \9 ~& ^/ y: [! i2 v1 O
            echo $cos[$i]."<br/>";- _% P, T' R7 ?( e$ E
    }
    6 g) }  f' e5 ?" N: J. s
    9 }( p  ~9 t! f- a; `这一步得到的结果是酱紫:
    $ D- [/ p/ `8 W  E1 R3 b" ?- l4 r3 t$ r% S2 M! ^
    将求好的Cos值排序,采用快排代码如下(百度copy而来):
    & `" ~  E+ @# u) B% w/ ]& P0 S& d* B8 W

    # W) `( F1 u) x5 W  f9 m; O+ \//对计算结果进行排序,凑合用快排吧先. B7 L9 H' R7 k4 K( i; ?0 g
    function quicksort($str){' u8 @% |2 L: b( X
            if(count($str)<=1) return $str;//如果个数不大于一,直接返回) t5 |5 C- K4 E
            $key=$str[0];//取一个值,稍后用来比较;8 Z. B6 K7 S1 E6 \4 h
            $left_arr=array();
    . F4 B! S) P: Q+ d9 i3 i        $right_arr=array();
    7 F3 B! d, ?; @8 f3 q       
    4 F' V  h( D$ A" O; q        for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;* @7 X7 }: q' W4 k
                    if($str[$i]>=$key)" i- k; u% E# @1 D/ b
                    $left_arr[]=$str[$i];
    5 B- L% w1 r. w" ?  h' w# N9 {7 F                else
    ( `1 @" v; m% V                $right_arr[]=$str[$i];1 n# |+ X8 @0 ~9 Q
            }
    & w" F, h( E3 K* ]5 |        $left_arr=quicksort($left_arr);//进行递归;
    0 r. d. v1 k- u. P8 Z5 f" K& F        $right_arr=quicksort($right_arr);) ~: V' l1 t& R8 F
            return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
    & \7 }. U3 ^% M# ^5 l}( z- ~  h+ T! U

    2 i! E6 ]5 w" p5 F2 l$neighbour = array();//$neighbour只是对cos值进行排序并存储0 l7 |7 [: A( F- {' c; X: k
    $neighbour = quicksort($cos);
    5 Q% @% Q# N) d; A2 P: p1 K
    9 ^1 M2 q* d3 U+ [9 c: h4 y# G  {! @$ H
    这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
    4 Y9 T! Y1 ?- A( w
    8 L3 j. a0 M, g4 B% {( M选出Cos值最高的3个人,作为Leo的邻居:
      \: U4 i3 _$ g
    3 a- ]* s# b7 p+ [6 w$ n//$neighbour_set 存储最近邻的人和cos值
    6 Y9 H  i2 R; O$neighbour_set = array();; D- H8 f; _4 G0 r$ G' k
    for($i=0;$i<3;$i++){( K/ [/ g$ m) i
            for($j=0;$j<5;$j++){
    " R0 O# Y+ T1 e                if($neighbour[$i] == $cos[$j]){/ e6 A3 W1 K* S& w
                            $neighbour_set[$i][0] = $j;
    . r7 V+ n8 {! w+ _9 h5 b# N                        $neighbour_set[$i][1] = $cos[$j];# Y& s3 s4 }% Y5 x( {' v/ d
                            $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
    ( s& r" [0 H  W* i0 g5 q                        $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    : _" M# W  p; ?3 S3 V+ ]; O                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
    : s( ?# u  z% R' Q( M  s                }3 u( C# W) E* Q( m( X0 W
            }
      I) m3 x+ F2 S- ^}
    0 s' F' r1 E" |, w; U( iprint_r($neighbour_set);; o) J# J' |$ _1 O6 l/ f# L5 ]* P# u
    echo "<p><br/>";# ]# i4 g$ O+ e% `9 A. N
    6 |) T7 z+ k  q. [
    这一步得到的结果是酱紫:
    / `) E3 M8 F: l9 q3 g' W/ u0 S' k& v

    6 H4 W6 T6 v' w9 U. e, C+ [$ ^* Z5 {. o) m7 N4 u* o' T
    转存失败重新上传取消- S6 B0 E, X: Y6 T; ]! c8 r% l
    # g, e5 l- T* }  E6 G) q# V( K6 p
    这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。
    & ^  M. K' }8 \) J
    8 B- M; g% B0 G- C1 i  h) p开始进行预测,计算Predict代码如下:
    ( `6 ?8 M  r1 {+ C, s+ ^8 x6 u& N1 f5 h
    我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
    , F% Z2 ^* c) b  d6 l
    2 p6 |' R( ~$ R" _& C//计算Leo对f的评分
    . d6 u4 f% B# c0 ]3 l$p_arr = array();
    , ^* d: p7 `8 X9 R- o, m$pfz_f = 0;+ `, m+ W9 [3 R2 o8 ]2 z: ?6 s# ?
    $pfm_f = 0;2 b$ I" A0 ^" E- j8 A3 H
    for($i=0;$i<3;$i++){6 g! w  r6 s6 M
            $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
    7 P- @& D% X8 V/ `; k2 G% t5 |        $pfm_f += $neighbour_set[$i][1];% G% R# i: X! P, H) Z* m
    }
    7 L* |2 v3 Z- }1 ~- X1 g1 R& P$p_arr[0][0] = 6;; \: K+ d1 f' n2 u* o% W- I) B
    $p_arr[0][1] = $pfz_f/sqrt($pfm_f);
    9 C& C% |' b) J' Dif($p_arr[0][1]>3){
    - ?, ]/ ^8 Q1 Z" `9 e3 O        echo "推荐f";) o' G4 b- v  Q# u! E
    }6 `8 `' ^8 E8 G, r
    - v( c( ~1 j4 i$ R6 f1 G. J
    //计算Leo对g的评分
    1 x# P  w8 Q# O; `$pfz_g = 0;# v# E8 k" F% P+ r! b
    $pfm_g = 0;! ~8 |- v" P+ e6 N  Q6 U) \
    for($i=0;$i<3;$i++){- j, ?+ Z: d& F* W7 X) g5 z% B
            $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];. ^$ X  w& g7 ]1 V$ _  {
            $pfm_g += $neighbour_set[$i][1];
    + Z5 Q* }5 z8 D- d/ c! ]5 P        $p_arr[1][0] = 7;
    $ X% X, a: F  J        $p_arr[1][1] = $pfz_g/sqrt($pfm_g);
    4 r$ v$ K0 `8 f- _}
    # @$ a1 }  d) b2 v; }if($p_arr[0][1]>3){
    - K8 T# M) {" n, ]  L. }        echo "推荐g";! C. J9 p( M, i8 x3 A3 L& i7 z
    }1 `& m- ~6 v- V0 P
    ( W% H! i1 C/ w! D& e: G2 ]
    //计算Leo对h的评分
    - i8 A/ U* h' Z' @$pfz_h = 0;8 C0 V( R3 j- j3 ?% n( S. q9 j1 E
    $pfm_h = 0;
    * R9 ]& i3 V) `$ v. X" A7 Hfor($i=0;$i<3;$i++){
    5 G) K0 I# }5 w- P; O        $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];. L; T' g* x+ G# O4 i# X  r0 I9 ?
            $pfm_h += $neighbour_set[$i][1];8 a2 H/ _7 W! Z! t
            $p_arr[2][0] = 8;8 D; b$ q: @8 N5 R
            $p_arr[2][1] = $pfz_h/sqrt($pfm_h);1 f' K+ T8 Q5 J
    }
    0 }6 T( A9 c) P# sprint_r($p_arr);5 X% i+ K( T; r6 O. c) ^3 |$ [! V$ ~
    if($p_arr[0][1]>3){
    3 @& a1 O0 e: i7 i4 N7 ~" ~4 [        echo "推荐h";" _2 X4 H/ k& c" S% U7 v
    }
    ( _+ K2 _: P% y1 C& r  H& f% h2 ~# z0 e  s" q
    $p_arr是对Leo的推荐数组,其内容类似如下;" T3 X3 s; J# G' b

    , O3 R; }7 u0 e2 Q2 R9 v- T& ]Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )
    : Z3 L3 A3 i% @9 K9 M. G' u+ K% @7 l0 }7 ~! W0 _( _) Z9 t
    f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........; O2 o+ n* N- A3 {

      B1 `& B" s2 }' n" E求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。  m2 m% L+ p0 g- G! ~& c% p- F

    $ z# t& w! T, L9 V1 S8 q& [从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:1 {! W7 I6 T2 Q* [7 y9 W
    # `3 p1 {  N7 \4 I. d
    1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。# N0 t( C) O" s* H  y# v4 i

    - x8 n2 }! r6 q5 h1 q% m2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。
    : Q5 J: N3 M, r! H( e# a1 N+ E4 A7 r  t/ c7 r  Z  ~
    3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
    1 q( m, B( v, s- x6 ^. k$ j
    " D) V" s8 l3 x8 {; O; g- h/ b4.可以适当引进基于内容的推荐,来完善推荐算法。$ V3 l0 I+ n+ O7 q4 e. l) q
    # Q8 g' A6 m, @2 v) B
    5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。
      P( E  _( k% G# c/ k' I————————————————
    , x- ?' N, Y% N/ E$ K  y+ P版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' c. U( F  k  P) y& h  S( H原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/1267154651 T% F- b" ^& R3 ~' p) \6 N% I

    # P9 h7 O! B1 I% G8 i4 [1 L7 U% E3 o! {
    7 P% P8 H4 }: U; k2 q8 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-4-15 17:18 , Processed in 0.761195 second(s), 51 queries .

    回顶部