QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1907|回复: 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
    - f* {5 m" P/ P8 m
    php+mysql实现简单的协同过滤推荐算法
    # G, q2 `7 _+ A仅做标记。。。
    7 p) n* }9 k) W$ ~- f1 r% s( o& u  E" W& g+ U% {7 w, N0 V

    % a; J4 P1 |0 ^$ k
    % x8 N% s8 Q1 |& D要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。, U. @  V+ F1 f7 H
    : r8 r3 W0 l1 d9 q1 C
    算法核心的公式如下:( x0 G$ x2 P: \6 V
    8 A2 O; T  r$ T
    1.余弦相似度(求邻居):
    . k/ Z; |& A# M$ k# N" a1 q' _$ y6 a% Z- `! O0 p; m8 q
    2.预测公式(预测a可能会喜欢哪种物品):
    - S, Z" O; ~3 V+ {+ j+ L  J5 P
    9 ]0 Y3 j2 E, y! c; T仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。% B( n( v6 r1 B% t6 m
      v- J/ m6 k3 p9 B5 ?2 C. x2 x$ Q
    首先建表:
    - q2 L3 y% M& O% c& e9 P+ ?) _3 H" _- ]7 k# \
    DROP TABLE IF EXISTS `tb_xttj`;
    % ^& _) U' H$ `, J0 sCREATE TABLE `tb_xttj` (/ g1 y+ q( j  w0 [! }7 E( d
      `name` varchar(255) NOT NULL,; j8 c: I2 W( x# T
      `a` int(255) default NULL,
    / J, o7 V& ]7 N8 W8 [: j0 K( {3 u  `b` int(255) default NULL,1 v9 `- H6 i  D5 B! {
      `c` int(255) default NULL,9 P3 @* B, J( W. R1 y
      `d` int(255) default NULL,  I- G% G# [; _
      `e` int(255) default NULL,
    " i6 v: M! I+ i& a& |4 q  `f` int(255) default NULL,
    ( s. S5 v1 `% S6 J! H  `g` int(255) default NULL,
    / Q  {& M/ d5 Y+ w  `h` int(255) default NULL,
    # P3 x  z+ m9 g5 k# F5 X  PRIMARY KEY  (`name`)
    ! a( Y& S& Q; f1 u) ENGINE=MyISAM DEFAULT CHARSET=latin1;, M" j9 `7 j1 v+ v0 c: w. |
    " M' @9 Z+ N9 q
    INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);$ ~/ i1 P/ `3 z
    INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);
    & H8 V( ^0 G) v9 l7 tINSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
    6 \+ `* T( j% s: N2 iINSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');# J! R5 {( r9 `/ X. l
    INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');9 H) H8 f* r  k/ Y: d" j' P$ l
    INSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);8 Y( z! \0 W5 L' @% v
      U3 j/ m9 f1 n3 G: i
    ; C" m2 c0 Y! @
    我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
    , v! J% O' i% l+ ~  u9 H8 B6 t3 v1 E0 t
    : ^( N  z" A$ i  A' C3 L    用php+mysql,流程图如下:+ T# K3 m1 |: c0 W# l# J& e1 D6 ?
    0 e, l! A/ J7 g1 e% d  q
    连接数据库并将其存储为二维数组的代码如下:
    + _/ T& d. H- `1 z- R; U, O* J* d, O$ r! j5 P! M5 a' C
    header("Content-Type:text/html;charset=utf-8");
    ' A* K7 q& @1 d" i% a) a  b* E7 @+ n7 ]( x: S
    mysql_connect("localhost","root","admin");& i! v: U) G0 w2 }% k- Y$ f  \
    mysql_select_db("geodatabase");5 `# j$ [5 L2 m# e; p& y
    mysql_query("set names 'utf8'");        # x1 h) O- N' U0 N2 `, z& B  ?
    # C5 J2 w% j# X0 [
    $sql = "SELECT * FROM tb_xttj";
    / g' Q1 j, s* R1 ]0 M& Y" p0 ~$result = mysql_query($sql);
    4 ~* W2 p2 m* n$ b1 a5 K7 s
    ; U5 ], D8 i) `+ S$array = array();
    0 G. y: `' k! y* k  d; gwhile($row=mysql_fetch_array($result))
    & v* a5 T. V3 F. `{7 l! |6 m2 J' J: U$ ~
            $array[]=$row;//$array[][]是一个二维数组* G" i+ t2 W+ p0 g
    } / t! t2 r9 A- @) I# s

    4 b) V7 I+ f) D& o0 T. T# R: Z问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
    0 n4 Q, x4 ~& \  |  w: C, J8 x( [7 ^0 S, Z
    求Leo与其他人的Cos值代码如下:( D! Q# N5 s4 U5 S0 o0 a

    4 V8 @( ?' L8 T: X. H! g6 \/*1 V1 E3 o" M  `5 F5 D
    * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来* ]- p$ N; [7 h# s
    */5 N1 L" X% c( Z7 _

    ) C% P; k) v8 V0 O/ C: c' H$cos = array();5 q5 i6 g8 U7 R" a. T
    $cos[0] = 0;
    " x, m4 m# f$ A! b$fm1 = 0;
    6 x' `6 _0 q/ f//开始计算cos* R7 X- i* T3 h4 g: ^% f: \
    //计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容3 A, y% H$ |' K$ N
    for($i=1;$i<9;$i++){+ ^! B, z, `8 R6 ^6 _) G6 M% j
            if($array[5][$i] != null){//$array[5]代表Leo
    7 e) Y7 \2 }: \& y' \$ {+ G9 t                $fm1 += $array[5][$i] * $array[5][$i];% e5 l8 W, m$ d4 Q$ d# f/ f
            }0 k; J# D4 }9 e6 M+ ^
    }
    2 {# q/ Z% ]  i# v4 ^
    3 D1 x. ?9 r6 z; c$fm1 = sqrt($fm1);
    ; p% n9 [1 G, u0 \7 N! A& H1 A' W% u% \0 m. f3 E
    for($i=0;$i<5;$i++){) r' A8 {' z5 Z
            $fz = 0;
    2 G5 N% `1 O! }: K$ |" H        $fm2 = 0;
    / |7 c" w' S- M* A- [- X        echo "Cos(".$array[5][0].",".$array[$i][0].")=";
      r% L% y/ P+ V4 s        % k* w( q; S: d7 ~; P" C
            for($j=1;$j<9;$j++){
    0 _* H# H5 V4 x            //计算分子) f* }4 b- Z" o
                    if($array[5][$j] != null && $array[$i][$j] != null){
    4 j* G9 J( R) q9 Q' [8 \                        $fz += $array[5][$j] * $array[$i][$j];, ?# Z& R* s/ c
                    }
    , m9 R, i2 @) ]* V                //计算分母2. c3 n4 x. a* O8 I: d
                    if($array[$i][$j] != null){
    & q) g6 r0 R; i+ i( L                        $fm2 += $array[$i][$j] * $array[$i][$j];
      o' J$ u4 B$ B5 ?5 E                }                       
    " G! Z" c" A* H: |! X% K' R        }; X& G/ `0 ^& f" v0 e
            $fm2 = sqrt($fm2);
    9 K* T5 C0 M+ |% r; E8 B, p        $cos[$i] = $fz/$fm1/$fm2;7 Z: ?7 e! W# Q2 H& t1 x0 N
            echo $cos[$i]."<br/>";8 N7 M* W7 v) n& w: f& a8 W
    }
    ! Q4 _3 M" G$ R% T6 P9 [: R4 N) }" o
    这一步得到的结果是酱紫:
    1 a& `8 r0 o- F. }! P' G* j0 e& Q
    8 S) A, Y" |% X2 S- N. K: A将求好的Cos值排序,采用快排代码如下(百度copy而来):
    4 M9 O8 d! w0 m7 X& R: X/ u9 R, `4 n' O  L
    / h) f* B  v( t4 a) ]. @$ t# v' ^
    //对计算结果进行排序,凑合用快排吧先7 c* W; ]1 B/ t- |. V" M
    function quicksort($str){
    4 o* t1 ?' \( t0 i        if(count($str)<=1) return $str;//如果个数不大于一,直接返回9 X' a  D# Y# M9 @9 a) _4 ]
            $key=$str[0];//取一个值,稍后用来比较;
    # h+ K6 X9 U% A6 |# o        $left_arr=array();( x- J# o! F  [: `- c# K
            $right_arr=array();+ g4 l; Q4 @. j, a
            ( ?+ d, Z- x* B
            for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;% x: }3 ?! R. X1 V" {! E& y3 T
                    if($str[$i]>=$key)
    ; |( {# \$ o4 J2 i; N$ K) U                $left_arr[]=$str[$i];
    & k( S9 Z7 e3 X4 c. D                else
    9 B% r6 |9 F0 I, x* K& e) `- [! _                $right_arr[]=$str[$i];
    2 M/ F6 u# n2 j        }: B2 }0 F1 `% W. i4 ^% F- C
            $left_arr=quicksort($left_arr);//进行递归;
    ( U5 E  @! C, C0 f" c, E, s0 g        $right_arr=quicksort($right_arr);9 W  n- c8 G/ c
            return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
    & j* b/ ~: j5 V& }}% E/ @" Q' L9 Z' b% [1 z
    - j& h* ]# H& A8 K
    $neighbour = array();//$neighbour只是对cos值进行排序并存储
    7 H! _* e$ c2 q0 Y8 E* S6 r$neighbour = quicksort($cos);0 f. }) E- P+ R6 A& f

    ; o6 |6 M# L' d' i/ J% A, ~* f* |6 P* j+ J2 w( j1 c' W
    这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
    % f% b7 B9 K8 w- [5 c
    ( a0 D, n" o% p选出Cos值最高的3个人,作为Leo的邻居:  a& _* @! h- S3 f, V

      \( p% Q! H% J# C9 Y  [//$neighbour_set 存储最近邻的人和cos值
    ' x. ~! a, ~/ k5 |$neighbour_set = array();
    ; M' s7 z$ Z* i0 z+ p# ]for($i=0;$i<3;$i++){' N& E: k0 G% w
            for($j=0;$j<5;$j++){2 Y5 o) X7 Y4 _9 Q6 ^. O
                    if($neighbour[$i] == $cos[$j]){7 N* c" n3 ^4 R  w4 ^8 i
                            $neighbour_set[$i][0] = $j;+ x6 J* T! s8 n
                            $neighbour_set[$i][1] = $cos[$j];0 A& x+ c5 O3 b% z0 L. _/ b
                            $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分+ N% N; |; ~1 d& \1 v! b5 o
                            $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    * G5 p5 E& m7 m5 S# {; A4 I  {$ _                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分( p/ b3 h# s% Q2 R/ q' |- L! d
                    }  `* f$ s7 V- K7 b" _. Y
            }
    ; J5 @- E1 Z! k$ Y2 a4 \! k! V1 U) k}
    5 X$ W! ^- s3 O1 ~! V' r% nprint_r($neighbour_set);: ^8 M# B# Q) S4 N( d) R
    echo "<p><br/>";
    # i2 O: ]% ~( ^( D" ]+ W  Q: S# D4 a  F3 r$ P# @, |
    这一步得到的结果是酱紫:* c; {, D" i4 l  _5 h3 c0 x
    : A  I: F  E5 z* p

    + E# w8 A' J" k& Z% j+ j% Y$ n1 G. m1 @! J
    转存失败重新上传取消9 d. L$ z6 O0 d
    # O2 d' I& w4 |* m5 x% e
    这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。2 u2 V$ |+ f& c+ k/ Z, H

    # w. U4 w4 q5 n& |1 @开始进行预测,计算Predict代码如下:
    2 I$ f. @% w- q8 v( c! G8 l  d$ F! J4 h+ |' N
    我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
    + u. H* a4 E5 v! Y! @/ l; n* N6 O( c1 L$ w
    //计算Leo对f的评分
    % E2 @3 W' h9 s& B: S- [& n$p_arr = array();* X! V9 ?5 g1 X% W. ^5 j$ V
    $pfz_f = 0;
    2 G3 p7 N. A5 d5 m3 j$pfm_f = 0;7 ~2 s! r& [- m  w, ^& v9 @; c% n! |
    for($i=0;$i<3;$i++){. r' z( w/ J" _* d
            $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];* t8 O9 Z$ K& c- _- A/ F+ D8 z! Q
            $pfm_f += $neighbour_set[$i][1];
    , o  |  z& k, A' O; |}
    4 t+ T7 `# N6 L) f& z$p_arr[0][0] = 6;
    , V  ~6 Z3 V( j' n$p_arr[0][1] = $pfz_f/sqrt($pfm_f);
    6 F. t4 w( S9 r* {0 h! wif($p_arr[0][1]>3){
    * [2 h' n0 _+ e8 k$ V        echo "推荐f";" R5 N$ h6 \0 g0 b$ R2 A2 q$ A* B
    }
    % f% @, ^- D- {* s2 t
    + D1 J3 I3 ~: Y& l0 P//计算Leo对g的评分: `6 |5 P! Z% F  k, C; ^5 M
    $pfz_g = 0;
      y7 {* o: Q- x& h1 h7 k3 V$pfm_g = 0;: n, T: _/ m3 p+ V
    for($i=0;$i<3;$i++){
    - T  d& u1 ]% {& Y& c" m" C. F        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
    . W8 C- U" S3 i1 i        $pfm_g += $neighbour_set[$i][1];/ ^! g$ S6 k+ G, C. k
            $p_arr[1][0] = 7;3 s7 M; ]5 B7 C9 A+ p3 z3 X
            $p_arr[1][1] = $pfz_g/sqrt($pfm_g);' u+ m  P* D% P
    }, V4 l4 T/ q; c9 ]* N
    if($p_arr[0][1]>3){
    6 x5 x$ w' r4 F3 u: ~6 @3 ^, O% d2 [        echo "推荐g";
    2 n$ j0 r$ \5 I}
    ! ?  y8 T: o2 a) H: J" c' g% j" g, s1 I4 C+ m/ H
    //计算Leo对h的评分2 I4 Q( B+ K2 I9 t6 Q, X
    $pfz_h = 0;+ p- O6 h5 D$ B+ T7 \; J1 K+ H$ a5 H
    $pfm_h = 0;+ a9 @( b% U* g0 B8 P8 v: N: r
    for($i=0;$i<3;$i++){. f& J  }: J* @: h, \" [0 V
            $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
    & u- k' w" f. o1 g        $pfm_h += $neighbour_set[$i][1];
    : _8 z2 U# T; A; u, w# r6 v        $p_arr[2][0] = 8;8 h# W. U) l0 m9 n0 h0 D& M
            $p_arr[2][1] = $pfz_h/sqrt($pfm_h);# b, @0 I" w: r4 w4 @  c0 a
    }
    + u# Z% D& G$ a  V0 Q5 kprint_r($p_arr);; W) U* l# U! H( H% F9 x
    if($p_arr[0][1]>3){& B* y% O' [3 G2 f9 A
            echo "推荐h";
    ( B* P/ M  _6 M2 T3 v6 V5 A. I( P}
    ; i9 Q2 \7 F9 e# X: }, N2 W; E; I! N. H, q& p  y9 l# T; W
    $p_arr是对Leo的推荐数组,其内容类似如下;: @; H/ f1 P4 V+ m

      d( f1 _/ l+ t/ O4 N3 m7 TArray ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )6 a* j8 I5 l3 J8 Z0 B# P+ y0 c

    . Z, n0 T  S" j6 |f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........
    2 I, e" f; h2 ]7 ?9 z( Q! T! m. `) e1 V. T
    求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。
    : I" i  H" F( N9 O/ v5 ^( Y
    & ], I  K! S5 B从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
    6 N) r& S0 S; |
    ! f1 X9 e$ |7 H& z1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。. r7 b. h3 v! [0 s$ T# O6 q

      [) o+ W! [( {. B2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。& C% f- B$ X7 f: B

    & a0 R) ?2 Q$ `9 D5 _$ `% \3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。: }+ q$ l' g9 I7 |- r/ V
    2 H$ G% n6 v$ `0 i7 ^" i$ Z
    4.可以适当引进基于内容的推荐,来完善推荐算法。; q! o: _* p: E

    8 D" Q9 V: o" r# r5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。% D6 s1 @$ G. Z4 U6 C7 `
    ————————————————$ S& s1 s, N8 {  Y/ p3 m
    版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    - ?9 w/ y2 F7 M. |8 u原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
    ; S$ `/ ?! _+ o5 l! p" H7 C3 f6 a' t2 C' [0 ^, o

    ! m' i" X3 I2 P3 ]
    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-5-26 05:15 , Processed in 0.426399 second(s), 51 queries .

    回顶部