QQ登录

只需要一步,快速开始

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

    4 h9 s' \) t% j* W/ s$ mphp+mysql实现简单的协同过滤推荐算法
    $ S8 F- K; x, O- f  ~9 P% X仅做标记。。。
    3 n% C& g9 w- J  b% k6 n! Y! Y  p& v6 ~7 h5 j1 m/ i) G( D- \! F
    / i2 F1 Z7 A% y$ b- o6 H* O
      p$ i9 p( K2 b0 p" }7 ]
    要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。- \5 N; ]2 q# e- S+ |- D
    $ W' k# c3 G# N" G- r  e
    算法核心的公式如下:
    - L( [# r6 m' N7 G' c& N! L! x4 g  K9 P$ {0 S0 m' n- E/ B4 G
    1.余弦相似度(求邻居):
    ) E4 \* |: Y# L/ V; g3 G) T
    6 p- p& S# s' _  k* {- u2.预测公式(预测a可能会喜欢哪种物品):
    9 V2 \$ g+ X& [5 h* u0 L7 r! i+ i) |& C
    仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。" D; M" o+ p* |5 S0 \% r

    9 d$ N' m' i$ p首先建表:4 n# F9 n; l) R! g: c7 y0 D2 y
    + d' T# h$ f* e: R
    DROP TABLE IF EXISTS `tb_xttj`;2 @& r6 t9 }; W' S" [' [/ f, J% G
    CREATE TABLE `tb_xttj` (. O0 U; H- ?/ T! L  j3 @
      `name` varchar(255) NOT NULL,5 M5 B" D* w8 s: h7 k4 I6 k" w5 J9 h2 B
      `a` int(255) default NULL,4 a; `+ l, ~6 a9 v
      `b` int(255) default NULL,
    ! F9 ~1 V1 g8 V5 r5 {9 M9 F; e; Z- |  |  `c` int(255) default NULL,
    5 a* J, P* n4 g) q7 B' ~# c  `d` int(255) default NULL,
    ! f3 L) n- F- d0 R  b  `e` int(255) default NULL,
    & R- @- W6 X9 Y3 b  `f` int(255) default NULL,
    ! h: J* ~5 z! A2 L" S  K  `g` int(255) default NULL,
    4 n( G# Q2 L) a6 E/ ^  `h` int(255) default NULL,
    9 u2 {# Z9 |9 L6 m( F) i" P  PRIMARY KEY  (`name`)0 ?3 r& o3 s- K6 H
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1;& a8 J2 Q) x) g- w
      _5 h6 V+ y1 ~/ o0 L5 V, S
    INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
    , l  M  b& ^' j8 k3 n& ^7 ^) X3 VINSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);
    + J" Q* w0 E  D) FINSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');; h  b5 n: f+ d# |$ G
    INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');( D- Y/ ]4 v) ~" O  |
    INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');* e& y" N+ o0 w  Z# q
    INSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
    * V/ ^1 @$ l" o$ L6 V4 u7 D
    $ |5 C% ]( a5 }% X5 B8 h4 a4 {( f3 }  x4 n
    我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
    0 m% m" S8 S+ C0 C
    5 ?" A2 F  q( Y$ D5 T9 j: m# y    用php+mysql,流程图如下:
    # D+ W; q; s% p: u0 M
    / O/ J% B7 W  M连接数据库并将其存储为二维数组的代码如下:
    5 g. E! N) X+ A1 O- ?( [$ \& ?% _- L6 M* s' S
    header("Content-Type:text/html;charset=utf-8");. p; N& d+ r  @1 h& h( p* b

    " o# q  ~8 ]. B6 H2 V, Bmysql_connect("localhost","root","admin");$ I7 I: o6 d- m3 K& P
    mysql_select_db("geodatabase");2 n5 v+ {( \3 R) A
    mysql_query("set names 'utf8'");       
    8 J& `; Y: O7 K, p0 p' C* U- E$ E" s% M& z: o% S. c: y
    $sql = "SELECT * FROM tb_xttj";; z5 c) O* M* z$ D% n2 n4 ^  ]3 c
    $result = mysql_query($sql);
    8 e6 {) }1 v& z9 l/ y9 n2 @, G# ^; C$ s
    $array = array();' g# b4 U) U5 F6 l% \
    while($row=mysql_fetch_array($result))1 E- r& g, N1 K8 H
    {
    ) l; x0 D# r- j) u/ V        $array[]=$row;//$array[][]是一个二维数组; W$ W6 e8 M6 H+ `! J; p4 b# n
    }
    ( H/ a  F6 W# v+ E5 M
    7 k/ e0 [$ \; Z8 y. h问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。, F# F1 }" |1 ]6 Z# ~

      w8 g; q1 @2 P4 A0 h: n7 z求Leo与其他人的Cos值代码如下:
    $ r- @4 J' y; [; ?+ S- X
    2 p! X% j$ F4 N/ S# |' ~1 ^" b/*" V4 i, I$ D( E7 J8 T% a. \& {6 _
    * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来: S+ H) G# u% [" ], x# ?
    */- I$ J; B# r0 V# t! z2 O

    6 f1 \6 v1 H4 S# E. H3 q2 t( n$cos = array();
    . u) U0 ^' M: @, u  s$cos[0] = 0;
    : d( }( Q' v  c$ I$ ?$ ]$fm1 = 0;- a4 S0 m! q; A% F
    //开始计算cos/ f$ X0 [, j3 l4 y
    //计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容
    : d. \- B* I" i2 x. Vfor($i=1;$i<9;$i++){' C, W4 i* T1 S3 n+ T5 {* v
            if($array[5][$i] != null){//$array[5]代表Leo+ u- _# s4 k( ]6 U. Z# j* ^
                    $fm1 += $array[5][$i] * $array[5][$i];, Z7 R' c+ l" J: W  Z
            }
    ) D  @# J2 g' v}& d/ D' p# d! R& Y4 e( K

    ( `( R6 U9 O. H( A, t0 Y9 q! B$fm1 = sqrt($fm1);7 n5 Y  c% P% ]

    8 l* \; n- ?7 o) N( Y" bfor($i=0;$i<5;$i++){: F( h5 t; e( x6 D
            $fz = 0;. _" r) M  J* q! h. ]
            $fm2 = 0;5 [, K0 P5 k* X* s
            echo "Cos(".$array[5][0].",".$array[$i][0].")=";! e5 Y% }8 W* d" @
           
      X. R2 ^8 v4 n0 P        for($j=1;$j<9;$j++){
    " s' j6 t/ K! ~! a6 M  C. J            //计算分子. B2 ?* m+ U# l. ]
                    if($array[5][$j] != null && $array[$i][$j] != null){* V" S# v1 H' c' R7 W; n% z6 t" w
                            $fz += $array[5][$j] * $array[$i][$j];3 t) ]6 [7 m: a" N" X9 @
                    }$ x* j, l2 ^! {) K7 c; A
                    //计算分母2! w/ G8 N. c+ D  L
                    if($array[$i][$j] != null){$ {: Z1 X+ i7 e5 f
                            $fm2 += $array[$i][$j] * $array[$i][$j];6 Z2 m" G. V6 c3 u# u
                    }                        * u( ^8 K; d  \: g% f$ g+ ]
            }8 h! I. B0 L$ V+ v2 e
            $fm2 = sqrt($fm2);- \- V9 Z) {( v& t3 Z& D
            $cos[$i] = $fz/$fm1/$fm2;
    7 P! ^, Z: Z# s* k        echo $cos[$i]."<br/>";2 s; v  ~) s. j# s) k& T5 S1 k& K
    }: q! j4 ]. P5 q  @5 X% A

    , f8 q# r0 [& I3 M6 N+ l# J这一步得到的结果是酱紫:
    0 _8 y7 A9 h! G- a( u% N! _
    + b+ @+ _0 R6 y将求好的Cos值排序,采用快排代码如下(百度copy而来):9 q* m: N! _' ~" J: p4 z9 @

    4 @$ s$ c# n0 S& Q6 W  Z
    % @4 N) O) I6 C  E, W' w! q$ @//对计算结果进行排序,凑合用快排吧先* T7 A+ Q" {7 w  S
    function quicksort($str){
    ) K2 U; g/ U! P. p: C9 ]        if(count($str)<=1) return $str;//如果个数不大于一,直接返回# {: J2 l. `% T% `% |' A* x1 s" j
            $key=$str[0];//取一个值,稍后用来比较;
    " m# H4 X" [5 a! l2 ~3 `        $left_arr=array();+ Y- O1 M" E! U0 w2 z6 `
            $right_arr=array();
    1 P7 w7 u) |4 D       
    8 o0 W5 Y2 r4 p$ ^, q" R        for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;
    ! ^) ]- u1 A: f% I7 U# D) Y7 s8 \                if($str[$i]>=$key)
    4 x8 K0 P" q6 f" h) B                $left_arr[]=$str[$i];
    ; ~6 L/ q8 ]' I$ r                else& h, f! H* ^' i* H7 y; k2 Q
                    $right_arr[]=$str[$i];
    & `: t0 E8 u  b        }+ U: I& w! S4 C# x1 ^
            $left_arr=quicksort($left_arr);//进行递归;
    5 Z* v: W* K/ g! L4 S        $right_arr=quicksort($right_arr);; B6 W* y) J) ]- y
            return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
    ' L; w( @* I9 ^; U' L9 ~}: L* ]( J9 a* ?- W5 `/ h# `9 s4 b* \* S

    + J; D. F& K% Y! Z5 g# n$neighbour = array();//$neighbour只是对cos值进行排序并存储
    " A- h  ]4 E" m! {( F" k" f$neighbour = quicksort($cos);
    & }& F" m# i! C6 t5 _2 N: A" g1 N1 [1 k/ T$ l2 z5 V" z8 {9 p

    ) H5 r% t9 G5 ?. d  [1 y8 |这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
    & `5 U$ A7 }' ^- d& s$ x" c- |- r2 ?9 H# Y  b6 ^9 I
    选出Cos值最高的3个人,作为Leo的邻居:
    1 z; j9 T3 _# E& H$ D* }# k. m, X5 N5 \. h0 v1 a
    //$neighbour_set 存储最近邻的人和cos值
    ) O" F# t  E% U4 d1 o& x" n$neighbour_set = array();* f9 U1 v, R" Q  l
    for($i=0;$i<3;$i++){
    . h- [7 \! W9 y( J        for($j=0;$j<5;$j++){! a9 A, @$ Y5 d
                    if($neighbour[$i] == $cos[$j]){8 u2 X+ \6 m, q
                            $neighbour_set[$i][0] = $j;& w- W' d1 f5 M$ H$ e& w+ D
                            $neighbour_set[$i][1] = $cos[$j];% x5 Q1 C' A2 B( A+ c
                            $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
    2 [# C4 O7 i6 q0 b0 N) e9 E                        $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    8 X0 e* \. k/ c! b                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
    . m+ e/ H& }4 n% F+ \                }
    ( w$ Z8 r7 |) c6 J" A# A( e        }: d8 \8 C& X7 O1 q8 n+ Z# r1 D4 J
    }
    - C7 p* Q; s$ O3 h. f! Q3 `print_r($neighbour_set);
    + G8 U- q- |0 e7 L. Wecho "<p><br/>";/ ]  K1 S" G& a% Q8 {0 G% Y) u6 {
    / P, E$ s2 v0 H3 \8 d* }
    这一步得到的结果是酱紫:  ^- [: q' G3 x, I

    : W( F# Y) V$ x& s& B- P7 v
    * l/ v# t" p2 K! r5 Q
    ) H8 w" M, x- q+ Z7 v转存失败重新上传取消. O( b; V# Z9 O2 Z7 M

    / l0 D3 A* b0 g3 z; G+ p5 A这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。( d) R. Y: M1 w9 X- p% `; z; u
    0 t5 _+ \- W' |4 k5 e1 s- M( o8 l( P( ~
    开始进行预测,计算Predict代码如下:5 v$ \3 \7 \* n" G
    ; c0 _) \0 ]: \0 g; P& C  Y
    我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
    % q; v$ H9 m9 l; j* H  M8 w- A/ K  r- e# f& y9 ^
    //计算Leo对f的评分
    3 _7 B7 \- n6 G  a* a$p_arr = array();4 L4 m7 _5 G+ N7 d, Q4 q* o$ N
    $pfz_f = 0;
    ) N; F6 i: w4 g+ A* h. ]$pfm_f = 0;$ t5 E" `# o+ J' f4 J& ?2 U
    for($i=0;$i<3;$i++){, \% l. b2 w% J( }$ E  ]& _
            $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
    ( b$ i5 t5 Z; s        $pfm_f += $neighbour_set[$i][1];
    $ o7 d9 Q* w1 R& j0 e}/ B6 D3 o! V- a; R2 t$ s
    $p_arr[0][0] = 6;
    , Y4 ]$ k- _) l; Z" N" ]) p2 R$p_arr[0][1] = $pfz_f/sqrt($pfm_f);6 u% Q: N8 J( d, H2 K! R+ U" T) f
    if($p_arr[0][1]>3){& U4 `, |4 \, t6 S+ q  G, i: W7 u( f
            echo "推荐f";9 }) a* \0 A$ F+ B8 \) }) Q
    }) @9 Z, P5 ]0 H
    . C: }* c- H7 Z' C' Q
    //计算Leo对g的评分$ I7 b  l. c7 j1 H' F6 w2 ~
    $pfz_g = 0;$ i0 q  a# Y7 X* ^( t* i
    $pfm_g = 0;- H' c. y" o- K8 m
    for($i=0;$i<3;$i++){6 N% a  x8 n( n; m; h$ a/ h
            $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
    / \+ l; C; T% x+ ?( Z+ ?  h        $pfm_g += $neighbour_set[$i][1];
    2 V7 p: W+ @- q4 r; c        $p_arr[1][0] = 7;; G7 |# Q7 p# o3 L  _- C3 e
            $p_arr[1][1] = $pfz_g/sqrt($pfm_g);& c) y! n5 I3 n  @1 _( e
    }
    + g  G& x/ Z- _3 b, l8 Nif($p_arr[0][1]>3){# _  _" v; q7 j- |# X
            echo "推荐g";/ }' h4 t6 o3 t' T
    }
    , Q; l! d8 ~6 t  I/ v+ E( {' ]. y0 M
    //计算Leo对h的评分
    , d' k: x6 O3 q! z: ]; l' Y$pfz_h = 0;
    . R2 M& k6 V5 @2 i$pfm_h = 0;
    / w# H/ {% _% W+ p0 R3 Y! Pfor($i=0;$i<3;$i++){* Z/ R3 k2 r, B. n9 e. X' D
            $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
    " v; a! r1 l, O1 M1 H        $pfm_h += $neighbour_set[$i][1];
    ) E2 P: k- Z9 U5 c5 R# M7 G0 K        $p_arr[2][0] = 8;: j) |& |) b% D5 w
            $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
    ) e- a9 [* S5 a% A- z2 _. ~}
    " p3 z# D' D. ?/ @print_r($p_arr);2 v+ k5 B/ L, U0 b& h
    if($p_arr[0][1]>3){6 N9 R+ u9 i- V2 f0 g
            echo "推荐h";
    . N/ X6 b) G& f/ o}
    ; \2 G2 E. l: N; P& Y8 I4 _* @( F0 c5 S$ l# e+ o/ ?* K5 l
    $p_arr是对Leo的推荐数组,其内容类似如下;
    9 d" K1 K* l2 N
    9 V: ~: x9 O7 Q! T/ a" m; a4 Q2 GArray ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )
    ' C6 |/ d) q4 ~$ H" @$ b
    / K, x: n8 |5 X: D) m1 O. }0 L* X! Jf是第6列,Predict值是4.23,g是第七列,Predict值是2.65........
    " ]  ]$ G# S6 t1 I* R/ F. S3 z. Z  @
    求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。$ e7 X- u0 @# a. [/ c

    ! Q' [* N9 y4 O8 k! A) w从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
    $ ~$ h/ G2 b& S7 C" H2 S' R
    ; I% A$ q, m5 b- M8 T1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
    ( [/ ~9 G' h. d4 @( @* M  Z% A- r& ^0 l. R/ c7 t
    2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。' g1 \) `% w" [2 \8 E, Y  Y+ u% T( g

    0 r3 P, T+ s+ {& x. V0 q3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。* c- }) z. ?9 G  h

    % X0 u9 M6 Z& @7 J" E5 v4.可以适当引进基于内容的推荐,来完善推荐算法。
    ; \% s  m: E& P) Q( O1 |, Y8 e
    9 ^. W# B6 }, F  n2 _- i5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。( d& i! u4 \# i0 i; P
    ————————————————
    4 A  P, R# g6 T( b8 R版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。" @8 I6 s  Q" D) y
    原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/1267154657 ~  e+ b9 D' }+ s( w; _+ V/ Z1 v
    # M; Z/ g6 {0 T4 I. |/ m
    , i' p- q8 U* Y# e
    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-9-13 15:28 , Processed in 0.733427 second(s), 51 queries .

    回顶部