QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1945|回复: 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
    % r6 i, j3 p4 m0 T# ?$ S
    php+mysql实现简单的协同过滤推荐算法
    ( C" b. G, m7 B& d/ S仅做标记。。。9 w" N; a. g6 U$ |

    3 p* d+ S+ h7 Q9 j! j( e
    8 p/ C/ ^8 ~. s# Y; `& @) ?" v* W& y; Q4 H0 V, y
    要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品  3.将a可能喜欢的物品推荐给a。% f6 [% Z+ i1 H

    0 P$ t3 n, g- V6 o2 J( P算法核心的公式如下:
    . T/ j; @, A/ v- ], S# b0 X  o# N( o6 q  _4 f% V% ~
    1.余弦相似度(求邻居):
    # k7 Q0 `, ]+ }" _$ \9 p$ C: P; M+ N: V
    2.预测公式(预测a可能会喜欢哪种物品):
    8 M' }4 Z! G5 ~
    * U9 H7 K; @4 ?仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。  H0 C- Q* m# O" v/ |" B

      ]0 }* `2 n! P8 j: q首先建表:
    , L' w' q5 D8 g7 V* H( @0 z+ [
    : v! I/ \* v# j+ h% P6 CDROP TABLE IF EXISTS `tb_xttj`;
    1 i$ ]2 T/ L, o( p) s4 QCREATE TABLE `tb_xttj` (1 N9 W# Q3 X: F! v3 F. O) |
      `name` varchar(255) NOT NULL,
    : u  T2 e1 G- ?1 l5 z9 i- H  `a` int(255) default NULL,
    6 ~7 ?+ F0 a$ \! Y; C* A' W- o4 L  `b` int(255) default NULL,
    " r3 s2 c# a& a  `c` int(255) default NULL,
    / M; x$ c) p8 g; Z1 b  `d` int(255) default NULL,/ j: `- G5 B0 b; D0 m2 a0 \4 O
      `e` int(255) default NULL,, _) ^; {) U( ^
      `f` int(255) default NULL,
    0 y3 v% Y  U9 S8 m* P" `  `g` int(255) default NULL,
      U5 c1 ~$ S( R4 X5 O; d3 g  `h` int(255) default NULL,4 j+ r" Y; u" b9 X
      PRIMARY KEY  (`name`)
    & c! T. o3 x( {( \3 C3 G6 k( P) ENGINE=MyISAM DEFAULT CHARSET=latin1;
    . q; S  v: p+ J" O1 \! W+ F' T* z) r5 y2 z* z
    INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);" X3 K' n3 s- Y2 h$ Q1 e
    INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);* W0 A' ^/ i) R. }6 w, G
    INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
      ^$ R$ f5 n$ }8 XINSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');
    1 D" V5 q6 F. n- T$ h" I2 w6 ^INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
    2 o; ]5 u% q% g. tINSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
    0 r) h1 y$ ]7 N% E4 v7 c: W# r1 y8 S! W1 M, ?- v6 E

    ( |) W: y( s& K 我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
    9 S" z6 E6 }0 M* b/ ]8 W* g5 `- [" L0 T
        用php+mysql,流程图如下:2 f( q4 _4 t' x' P% E; V7 \% }) x
      d5 I' C0 F' E: M) q
    连接数据库并将其存储为二维数组的代码如下:# B! g1 h3 W: W# Z; f

    . O  A' k. e  V6 i" x& ?& aheader("Content-Type:text/html;charset=utf-8");
    , `* h) ~( u0 M- C9 r/ `& m# k5 F$ B  S+ d4 m/ E/ [- t( V6 B
    mysql_connect("localhost","root","admin");
    7 G$ E- }6 g8 g) @2 b$ i1 Cmysql_select_db("geodatabase");
    $ X" M4 k  ?5 k9 R1 `6 o0 emysql_query("set names 'utf8'");       
    & J3 @0 @! d$ Y' W2 _' R2 T: r" ?3 r: @
    $sql = "SELECT * FROM tb_xttj";
    ' j- o% u2 q4 z; i+ D% p  l& e$result = mysql_query($sql);8 D6 x7 [2 ~: X0 v5 N

      U! ]; X5 Z8 x& l) `$array = array();2 b+ T. u' s% x# w! y$ Q7 S
    while($row=mysql_fetch_array($result))
    2 ]" z! l! x0 x3 i, h{
    - D: I/ X: v8 o% `9 f        $array[]=$row;//$array[][]是一个二维数组
    $ S. R/ c) G5 o} 7 u9 Q8 N3 v& S5 p" @% N

    ; W  E3 h9 f8 n5 X; L+ o7 {# }- X问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
    , o/ O: w5 S: Q- V1 K
    7 i, ~! ~4 q7 T/ _( ?求Leo与其他人的Cos值代码如下:
      F4 |" r9 j" _3 B5 h
    8 o! s) k* o' X/*
    ) `% J- l* a- N, H% g% n * 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来% W' Z6 y9 k% j2 x0 A$ n/ z
    */2 E8 I$ X: ^4 x, J, k) S& h/ d

    8 \: L& M' I: }7 f$cos = array();, Q4 U8 ]) [* K
    $cos[0] = 0;
    1 V' E$ D$ n+ ~$fm1 = 0;* {! S, B' M: [! p- ^( Z
    //开始计算cos& G  s3 |7 a0 U4 G/ s5 V! T
    //计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容9 O0 J; ^4 g, I# {9 @) C  D
    for($i=1;$i<9;$i++){9 d$ d  N; Y& x  {8 |
            if($array[5][$i] != null){//$array[5]代表Leo
    & v6 g$ |5 u  L- L& I                $fm1 += $array[5][$i] * $array[5][$i];
    3 s1 W8 y% ?& Z* V        }1 |- C; a8 b% c" n/ i
    }; ^! I# K4 @" t; }; l+ Y
    5 z$ _2 U5 O* ]* }" B7 X  o! D
    $fm1 = sqrt($fm1);, W4 s" C5 q/ V2 C) |" _
    ) g$ U+ w6 Y% L* w
    for($i=0;$i<5;$i++){
    9 D, L# F2 t! n. G; B9 q7 R        $fz = 0;' B$ n' G# E; j- A" G7 w
            $fm2 = 0;* ^( V! c6 m- O& @
            echo "Cos(".$array[5][0].",".$array[$i][0].")=";3 q+ z4 b; e- [" g( b$ Q
            & U; {# m) F" Z6 e0 z% S, J
            for($j=1;$j<9;$j++){2 |: W, P/ D6 L- I
                //计算分子% C0 ~; k; F. j; [6 c+ Z
                    if($array[5][$j] != null && $array[$i][$j] != null){
    & d" I& i7 `& t                        $fz += $array[5][$j] * $array[$i][$j];
    * o+ @4 y; m0 y2 z5 n: ]3 [' b3 e                }
    . S# F( q, D" l4 C. [. s                //计算分母2: Q2 U6 D! M8 ~" ?2 y1 E
                    if($array[$i][$j] != null){
    6 y% y7 m# Z5 v6 Q+ b4 B$ @                        $fm2 += $array[$i][$j] * $array[$i][$j];6 R8 _. x" R0 G  X; Y( t, u3 y
                    }                        # I- R  ?1 P. C' y
            }
    8 p1 c( N% C' I        $fm2 = sqrt($fm2);
    2 I8 w6 M- }- e+ x# \        $cos[$i] = $fz/$fm1/$fm2;* Q( ^$ f% U/ C( t
            echo $cos[$i]."<br/>";
    & ~  P$ N: S+ i! }$ I}
      y. V  ]$ r/ m5 g! w& r5 r: d9 w3 t+ r( j& d
    这一步得到的结果是酱紫:; K- u% |6 P( i, c
    ' I2 v7 _7 q$ z* b( E
    将求好的Cos值排序,采用快排代码如下(百度copy而来):
    0 L$ t2 w- r  X' D9 U
    ; N8 x) {1 f7 d  Q  E5 R$ f* F9 l3 v
    //对计算结果进行排序,凑合用快排吧先: ~$ e  I5 f7 T" p% D, _4 P
    function quicksort($str){
    4 c9 x$ |1 {% ^$ q% p  Z; y        if(count($str)<=1) return $str;//如果个数不大于一,直接返回
    ( O; ]5 o+ B& I        $key=$str[0];//取一个值,稍后用来比较;0 s+ b' `: \9 g" w7 n
            $left_arr=array();
    , x8 A( ?5 R/ b" u. J' E* p; W4 y        $right_arr=array();
    / E" n( f- t6 ~3 O3 Z3 Y: o       
    5 @2 Z, k4 z, R' ]        for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;4 F/ A, Q3 |; {; f$ O8 A
                    if($str[$i]>=$key)+ i3 O- i- t8 v* i  y+ R1 s1 j1 R$ k
                    $left_arr[]=$str[$i];5 S- |: R% z, u, @2 \9 d
                    else: v$ k  ?* i# j3 ^4 \8 j$ H# N
                    $right_arr[]=$str[$i];
    $ ~+ s% ^+ `  o& O* n* n        }4 f4 V& L, U8 C$ X& {* n
            $left_arr=quicksort($left_arr);//进行递归;! y5 Z( C: t: v0 X8 t$ y+ q
            $right_arr=quicksort($right_arr);
    6 d9 g5 g* [& ~) q* ~6 q) c3 a        return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;+ ?' _8 s4 l! M
    }
    ! m; Q" Y3 z. d, p1 a) C! e2 }
    , D' T/ _, G, Y" r0 _& O( R$neighbour = array();//$neighbour只是对cos值进行排序并存储
    ; f5 }& b2 |+ V! H0 D9 ]% d$ w$ [$neighbour = quicksort($cos);
    ) B- o/ v* V8 ~6 G6 f, z. o/ U) g' X) x$ i3 |

    " g, K2 G: H* y0 P; c/ x这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
    . p1 d& i* L) E5 N0 l8 [# P* P
    : r3 F8 G1 A# D3 j- L) E+ Y选出Cos值最高的3个人,作为Leo的邻居:& E( z" p6 W- x# R3 z2 K$ K

    4 Q4 D6 }/ _6 s" W! D& z, ]//$neighbour_set 存储最近邻的人和cos值
    - Y- k- Y- [' I; `# m- P$neighbour_set = array();) v3 X- G/ f( T
    for($i=0;$i<3;$i++){
    ( {3 p! |  a: l, r* o        for($j=0;$j<5;$j++){
    9 x( D. V8 d. u+ x) V/ n                if($neighbour[$i] == $cos[$j]){
    : v! Y' h; i9 n% J- N% R9 O4 R* [% M                        $neighbour_set[$i][0] = $j;: _: i4 ^+ _  {0 e6 i
                            $neighbour_set[$i][1] = $cos[$j];
    1 R' n# `& z0 w5 Y' H1 T2 V                        $neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分3 f8 G, w# B" _# s: Z6 c/ z
                            $neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
    ) q1 v) q) |9 t6 Y0 [  a                        $neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
    3 i- G3 `0 Q0 n: V: A                }
    0 [5 ]# n/ _; L; T& c( j3 w        }; \. {$ c: J: U8 A, L5 N
    }
    9 S  j% \- W8 q1 e) dprint_r($neighbour_set);
    1 ^1 f1 ]: Z' `$ Q2 Mecho "<p><br/>";
    ' a. ^% ~4 `! K- {( V+ I2 P% R- W) [; \6 u8 Y9 e1 V# R  H
    这一步得到的结果是酱紫:8 {$ I0 J! u9 f
    5 s. |0 _3 r" {$ u( B. ~! B

    * u6 H6 P) e+ ]' [* f5 O. ?8 l( z( V/ g# ^! k1 @
    转存失败重新上传取消
    * |8 v: }& g# F6 y7 Z% t2 p7 C% u9 A9 ~
    这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。2 t# h7 q$ g3 S
    # z2 e  z$ k3 ]8 c. o( V  v0 U+ Y
    开始进行预测,计算Predict代码如下:6 f8 o. D$ X: a8 {$ b; p
    8 V7 `, ], a% e1 `( x# Q
    我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
    0 ]# L; r5 Y$ O/ C, w) j
    1 l, R; Q! ^/ d1 t" L//计算Leo对f的评分+ x- z9 ~0 l/ b/ ^( s5 |
    $p_arr = array();( E1 x" t7 H2 V& y  p! X7 i
    $pfz_f = 0;
    : B1 ^, [. q7 s5 d3 C. ]% _$pfm_f = 0;3 J2 N  C" H2 l: m4 D# C. q
    for($i=0;$i<3;$i++){
    2 T" {4 @6 w2 D        $pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
      S: `' L7 [; K, g, M        $pfm_f += $neighbour_set[$i][1];! N8 @3 B% t6 q* s  S4 i
    }
    ; F- a3 F7 ~" g$ \2 {$p_arr[0][0] = 6;
    & H) Y  a8 B/ l; h4 |, E+ R$p_arr[0][1] = $pfz_f/sqrt($pfm_f);' ~1 D6 b- \/ C; o1 M0 t
    if($p_arr[0][1]>3){- z9 v; i0 ^  I# Z% v% ~
            echo "推荐f";) B) }% o1 U1 r# ^  I9 c8 C
    }7 K# ?6 N1 x8 V. J" ~0 j

    , b7 Z* V0 N4 E+ _* t//计算Leo对g的评分$ n9 r9 F" ?" D4 Z5 m
    $pfz_g = 0;
      B* x/ }0 v- L$pfm_g = 0;
    " B: T, Z$ y$ G  u, ]" O( H0 L, rfor($i=0;$i<3;$i++){
    " I2 W5 {3 |% J- r9 K% I5 f, H9 t        $pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];& W. k2 O# X' C
            $pfm_g += $neighbour_set[$i][1];
    # B2 S6 y& l3 D$ o! c8 S        $p_arr[1][0] = 7;2 @* D! X, i) _/ L4 P; }" ^
            $p_arr[1][1] = $pfz_g/sqrt($pfm_g);
    : e2 x, R+ v1 {- g, W+ V+ C}
    $ V: R; A) D5 g  q' X3 O3 |4 D% pif($p_arr[0][1]>3){
    6 e/ X% ~; ?+ ]; _        echo "推荐g";
    9 l, `1 Y4 K. b  J3 {2 h* t}; a0 Z1 w% ?$ m$ x' y5 e! u

    ) }9 z; a1 c$ _( G4 Q9 G//计算Leo对h的评分
    ) L' o1 {2 j# X: \5 V$pfz_h = 0;, i9 w& S. Y* K: F  c) ?: J
    $pfm_h = 0;
    8 F: j$ O6 \2 L7 c$ s, @for($i=0;$i<3;$i++){% X9 \; s; T& A/ \5 n
            $pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];, W- F7 k  m) p
            $pfm_h += $neighbour_set[$i][1];3 ^/ A2 W2 p: F/ p$ R: f; S8 S4 a
            $p_arr[2][0] = 8;7 `/ q# `4 y. E* D: @- G) L1 Q1 h
            $p_arr[2][1] = $pfz_h/sqrt($pfm_h);
    : W$ r" H8 ?2 _}
    & Q) X$ i7 o' e* }- Xprint_r($p_arr);
    4 U4 _( f, _4 T/ ^7 aif($p_arr[0][1]>3){  e1 v0 q1 u6 t
            echo "推荐h";- ^1 D, ]3 T- B- w5 g
    }
    * R6 c% T: k" P4 v4 d3 X4 |5 B( i" {: x# z: L( w/ a
    $p_arr是对Leo的推荐数组,其内容类似如下;
    : T0 d  ^4 B' O4 V
    . ?' }: h. b9 U) _) |+ ]Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )4 O& i9 G8 O" c: ?# s* I6 x
    9 `0 p% O0 `3 d2 Q/ ^1 h2 _  h2 u
    f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........2 d8 {/ G9 F' N$ b; A

    1 C, v7 S% j+ h" |) e9 x求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。
    ) Q* f1 [* ~8 l  J
    : B0 }1 l' _4 o6 g) _: N从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:- {) ~, z6 _2 @2 U! R" x4 J
    + R$ k5 s$ H" b; O! Z6 P
    1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
    6 R2 Q8 m  m: S) h+ b+ X5 s* Q4 Z4 I0 J% }
    2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。
    7 i' g1 x' _7 H" l7 b# o% x% v9 h# [$ E' `* ^0 I! P
    3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。0 q& k: ^" C+ b4 q( K. e
    + j/ B- F. m) O3 ^
    4.可以适当引进基于内容的推荐,来完善推荐算法。5 ^5 T! S5 i! N0 J1 v1 |

    5 z9 b) D' S# l# c5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。
    1 @3 w4 A2 ]+ c: n. o) [$ x————————————————
    6 ]2 ~$ p$ S# w6 T# e版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' `! G+ n: A7 N1 s6 K原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
    , C4 V( o" ~0 k
    6 Q. j3 L6 B" n5 }+ |
    ) S2 ?8 Y2 }! ~
    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-8-5 14:30 , Processed in 0.420996 second(s), 50 queries .

    回顶部