数学建模社区-数学中国
标题:
php+mysql实现简单的协同过滤推荐算法
[打印本页]
作者:
杨利霞
时间:
2022-9-8 10:21
标题:
php+mysql实现简单的协同过滤推荐算法
5 j& z! q( y5 T5 t& J$ W
php+mysql实现简单的协同过滤推荐算法
* O; H: D3 D9 f, x( U) a
仅做标记。。。
, T, M$ ~( \5 Q& _- n# l5 K4 S
. o, f% w8 s* H: O. t! F7 R
" q% U h3 l9 H6 U2 P5 J0 D
* [5 I% a4 {/ E4 x5 o) y" `6 M5 d& Q, L& ]. Y
要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品 3.将a可能喜欢的物品推荐给a。
; I% p# m! R; x0 m: F( T G
3 X& ]2 ], l: N' D
算法核心的公式如下:
( E; T' t1 M% U1 D0 j3 c7 F
; z! G. y+ P& w, I1 w( \' ^8 {
1.余弦相似度(求邻居):
1 ~2 z( Y9 a" P
1 M4 y/ s: ]9 W) v1 h
2.预测公式(预测a可能会喜欢哪种物品):
$ T7 G) p+ l& a6 w$ e3 P
\1 Z, [5 L+ Y8 F$ u5 w
仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。
( t) h2 S: q+ f& q4 r
+ q! H( F/ i* b! Z0 c: E
首先建表:
. }6 D7 e) ~8 {/ b+ Q9 E, U% s
- b) p/ n' p) d
DROP TABLE IF EXISTS `tb_xttj`;
4 W3 M. s- t7 ?4 p: ?; ^ h
CREATE TABLE `tb_xttj` (
/ k) X2 [- ?/ L/ V
`name` varchar(255) NOT NULL,
6 l5 z% `* @, ^5 D. j/ D
`a` int(255) default NULL,
+ e) I# Q7 h9 m
`b` int(255) default NULL,
5 Q) }2 F( [: y! a/ N! _
`c` int(255) default NULL,
( w1 ]: o$ Y2 ~
`d` int(255) default NULL,
$ T9 x; J, P# y4 K8 B' _
`e` int(255) default NULL,
; M5 W+ e8 `% [+ E2 m/ Z$ q: d" |
`f` int(255) default NULL,
: }+ l0 u2 B Y. Z5 Z6 ~
`g` int(255) default NULL,
0 H) [8 t) h8 f! a; D' U; X6 U7 a; ^. O
`h` int(255) default NULL,
: F. Q! ]5 ?: l& |
PRIMARY KEY (`name`)
% \' ~0 e% ^6 u+ ~0 i
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
; \. D6 P. m) m: R) t& q! }
" z" }8 v' G- Q* P0 j! a, r; Z
INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
5 Y8 a' B6 E0 @- u, V
INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);
! G+ m- o* k3 ~5 D$ _
INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
4 }' F$ T7 @3 R! B' G5 {
INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');
0 ` o( a! x) Q: E" H
INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
( A; \8 W) `0 G7 e+ f9 }
INSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
$ A8 \) Y O, \
( @2 T* c8 _2 G' C% l; _1 o, q4 j
( W2 O. j! m0 s( @9 A) a- h( _ P
我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
% W! {( c4 y9 y* X( S, x
5 ]* ^- n" f; T
用php+mysql,流程图如下:
, I+ S( b/ {& x( K# s( x% W
% w/ l/ I8 D# l, f
连接数据库并将其存储为二维数组的代码如下:
. W- V/ g, |$ o7 n
. }* |: ^; s4 H* U. ? S
header("Content-Type:text/html;charset=utf-8");
/ n+ U$ P O+ s
4 ` k3 P7 }5 b% `- O
mysql_connect("localhost","root","admin");
, V# }! Y8 `% D
mysql_select_db("geodatabase");
. T/ L3 p, G# }' l, d, ~& Q: x; N, I
mysql_query("set names 'utf8'");
9 G3 c: z7 q- y+ r; r1 G
' e" b* @" Q+ f* q) B( }4 p
$sql = "SELECT * FROM tb_xttj";
1 l7 T4 Q0 I% D
$result = mysql_query($sql);
) b5 u! X0 ]4 h$ r. O S
0 h( b) O, k& Y) ]* Q" v$ W/ P
$array = array();
& S2 q( c7 g. K8 ~$ S, X8 `
while($row=mysql_fetch_array($result))
3 W# q6 q6 W4 Z3 h7 c* m& u
{
7 g7 o. q& K3 M+ V: b: X% ^
$array[]=$row;//$array[][]是一个二维数组
' O: {" E7 e! [$ C
}
( |6 j O1 e0 [( t0 i
& w$ s( ^! ~- ?9 p) F
问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
2 A9 t' v# ^$ w7 P3 o
& w5 a( A0 M, g) c' F9 o. P
求Leo与其他人的Cos值代码如下:
+ V/ J! ~& ]& I$ Y) A
% c, N6 Z! L4 [2 F' k) I
/*
" B. S' k _5 |: a9 z
* 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来
+ P, L2 H: g* _7 L
*/
7 [8 Y. ]" t# ^% o7 C* r4 `
! ]/ F8 j# d& h: U6 w
$cos = array();
3 G2 i# S" `& H0 r
$cos[0] = 0;
; N. p$ t( t- I+ V( f/ i: q! w
$fm1 = 0;
0 {# o% `0 P8 p9 ~/ B, x* ~6 D+ A
//开始计算cos
5 Y2 X+ y; R. I4 B
//计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容
& R$ V5 w' J, @
for($i=1;$i<9;$i++){
+ J G; V; S; f( ^
if($array[5][$i] != null){//$array[5]代表Leo
; `% d2 _) O* u3 R- q
$fm1 += $array[5][$i] * $array[5][$i];
# }7 w" q" ~, P1 @4 o+ w4 i* ~
}
9 @: P, o- G* h$ \' p6 n
}
0 p K x& W. W6 L4 x* j3 |) D
8 J; l, d0 } l6 I% e% f: E
$fm1 = sqrt($fm1);
$ W) l$ l; K3 g* E
+ a/ C6 j* j. j v+ ]7 L2 K/ `
for($i=0;$i<5;$i++){
* ?9 E+ z* i3 u6 `$ Z
$fz = 0;
; F/ r7 B- C7 E% x# ~% j$ {" @
$fm2 = 0;
' z! D* ^# I# w6 Q; G. q
echo "Cos(".$array[5][0].",".$array[$i][0].")=";
8 @: I7 | P/ I9 [! }0 W7 O
3 ~: z. x) e) D# v) o9 h
for($j=1;$j<9;$j++){
" a; D2 T w) {6 M
//计算分子
* _) v# s# m: u; V* V& w
if($array[5][$j] != null && $array[$i][$j] != null){
; `, ]# p* ~/ V8 F) |' Y
$fz += $array[5][$j] * $array[$i][$j];
3 T) B8 y1 `) }4 n( E6 Z. g0 u
}
5 n( A \7 [: j- j- Z& `
//计算分母2
% g2 I9 i0 Y9 F9 m* P f) {) F R
if($array[$i][$j] != null){
8 ]+ U" |/ c8 t
$fm2 += $array[$i][$j] * $array[$i][$j];
K# O1 i/ c3 }# ]6 X$ s: b/ T' F
}
5 ~! y& J$ P! D4 J8 C, W4 ~" }& Q8 D
}
+ U+ o0 z: Q+ s
$fm2 = sqrt($fm2);
% y5 w) V' W. l: s
$cos[$i] = $fz/$fm1/$fm2;
& E+ @& {; _' Q2 {' o
echo $cos[$i]."<br/>";
$ [ W& ~" K/ A
}
, ]4 T+ r% l& Q' Q
~; L2 S5 g9 r; g3 A, X U
这一步得到的结果是酱紫:
% C6 `9 f# q& A
% x/ M( ? d$ t1 t
将求好的Cos值排序,采用快排代码如下(百度copy而来):
; }) ?& H% _+ v% c/ K' T: w
* X+ C- E3 g% \ Y+ g
6 c5 o8 Z8 `" h: a
//对计算结果进行排序,凑合用快排吧先
! Z. m4 k; h q B
function quicksort($str){
5 p# u& Y! T/ a5 Z8 }; T
if(count($str)<=1) return $str;//如果个数不大于一,直接返回
: L7 j0 W! C$ \) O6 F7 z& O, r
$key=$str[0];//取一个值,稍后用来比较;
: ?( U o7 k( n
$left_arr=array();
" X* s$ `+ v# U0 ^" T5 C$ R0 Z g
$right_arr=array();
3 j) z! n, c) h; W0 V+ U( n
3 {$ e# Y, G7 Y' r7 e6 N% W
for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;
1 j+ d A O) ^5 {! `1 K" e* H+ m: t
if($str[$i]>=$key)
1 b0 ]' q4 j# p1 O$ F2 o. c+ B
$left_arr[]=$str[$i];
" ]$ j1 m# o! M6 y( H M* @
else
: E' ?1 ~: d9 o; q1 Z/ l
$right_arr[]=$str[$i];
; |$ E( G" d8 c( e" y+ K( j+ I9 k
}
( D/ |9 [# z- c# A7 @
$left_arr=quicksort($left_arr);//进行递归;
3 D B v# |7 P* A2 [
$right_arr=quicksort($right_arr);
, B- A$ R& x! `1 C: s; A/ L
return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
7 p f& s5 h2 R- Y3 O
}
4 s& q, c# S: G: G0 ?
9 g5 o) d9 H1 r# a
$neighbour = array();//$neighbour只是对cos值进行排序并存储
- P9 H" k; D5 f! q$ I* s( i3 A
$neighbour = quicksort($cos);
, f; U0 [! x/ j( A8 M; P
" K/ O% k7 {/ f9 k9 {7 M1 Q
4 n# x% W) J8 [+ d5 v: a
这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
5 h2 \$ g3 B( p, F) |
D9 r$ r: ^/ I
选出Cos值最高的3个人,作为Leo的邻居:
7 ]' Z. `1 C* [- ] A1 y5 [ b0 h
9 ?0 t \3 U/ Y
//$neighbour_set 存储最近邻的人和cos值
2 M0 E( Q# U. d) ~+ j! ~' Z
$neighbour_set = array();
7 V1 L- B b: `" V. z& x) s
for($i=0;$i<3;$i++){
, v! j- X& {% Y" }
for($j=0;$j<5;$j++){
: g& a; p% Z6 j1 p) j' A; O
if($neighbour[$i] == $cos[$j]){
# F9 y- @8 ~% Y' _5 A& O) p7 X& I6 D. L
$neighbour_set[$i][0] = $j;
3 k) K- J4 e, `4 K
$neighbour_set[$i][1] = $cos[$j];
: x& V2 F, \( e/ ]
$neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
3 L. L+ v- D, s+ s% D
$neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
8 S/ E6 |- I1 |0 b! m
$neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
/ r6 w& }7 `' L6 H( t. }4 s# I
}
& k [5 h/ I8 Y
}
" D: a& a! A' C' G4 X) q! M9 t
}
5 k% \5 d: P4 [; Y% S5 e
print_r($neighbour_set);
3 [: ]( }! O" x8 Y9 e5 m
echo "<p><br/>";
/ J+ F& d+ i5 f$ w& C1 l+ s- _
/ G* [: c7 u, d9 T A/ a! |
这一步得到的结果是酱紫:
& o% \( h h: k& o
- W! _9 E+ l( T8 b, O7 I: w2 f9 n
% a2 Y' g8 b4 }1 y% u+ M/ H
. c6 [1 _3 f% e: N% x
转存失败重新上传取消
! Q# e/ b( R- D
) w3 A0 \$ `! P; ?, V+ S) E& j
这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。
( ^9 o0 K) ^' n3 e4 ?0 I
3 M) a, L/ i: c! K a; H+ I4 g: a
开始进行预测,计算Predict代码如下:
[$ `4 ?( C& x+ g
. ]# T% T2 N6 m) D# K( b
我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
3 j* M9 }# q( L$ \6 f$ v
4 x4 E9 X4 w; G6 q# n
//计算Leo对f的评分
+ p: c# y- i. j( B( v2 S1 `
$p_arr = array();
9 X8 f$ b. E7 d
$pfz_f = 0;
~9 n/ ~& E/ g4 g' L
$pfm_f = 0;
, ]' O% A0 @2 h% p
for($i=0;$i<3;$i++){
& j0 B/ q- `! c6 \- n
$pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
0 H) X, Y. S9 A2 ?8 C: T
$pfm_f += $neighbour_set[$i][1];
4 S+ O4 _! p/ a% a
}
7 k3 O+ m& D H6 X+ w/ w
$p_arr[0][0] = 6;
: f' L6 o. Y; Z' p
$p_arr[0][1] = $pfz_f/sqrt($pfm_f);
! U8 I7 T w: q, N
if($p_arr[0][1]>3){
- Z- J" w0 ^2 t, o' k
echo "推荐f";
9 M( n& i0 C: x) I: w9 H9 M* C. d
}
' `2 ^& W* Y, r) c; @4 q' @
9 A$ B3 a8 u8 z
//计算Leo对g的评分
6 i& V0 q$ T3 M
$pfz_g = 0;
: a9 l" j n" I g2 u, j
$pfm_g = 0;
/ ?" j& a& b9 ~% h3 I4 m3 _) t
for($i=0;$i<3;$i++){
' }4 y* o2 I# X
$pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
0 q( t* \. k( W% K0 k
$pfm_g += $neighbour_set[$i][1];
. \" ~3 L3 n5 h4 K) N; J+ V
$p_arr[1][0] = 7;
" P& B: Q* d2 G4 v) T# k" ]1 [0 N; u
$p_arr[1][1] = $pfz_g/sqrt($pfm_g);
3 Z% i( ]2 P: j
}
8 Z( }1 I2 B h8 U0 ]6 t
if($p_arr[0][1]>3){
7 L6 }1 x8 J) \) n
echo "推荐g";
9 E1 h% F" o3 ]5 }3 r+ [
}
# q; |, p9 _7 E# k
2 y! h( b1 N1 g% l+ S7 {: ?( J" Y- P* ~
//计算Leo对h的评分
+ W8 g' N" x6 X* w" V
$pfz_h = 0;
I* W) w6 b, u) V" D
$pfm_h = 0;
# X& y7 @; ^& }/ o5 u3 k
for($i=0;$i<3;$i++){
# s. @7 {9 L$ g& i4 O" r
$pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
z) k5 e1 _: S5 F8 v: R
$pfm_h += $neighbour_set[$i][1];
& z$ ?3 E! }4 q) U& v, r( ^
$p_arr[2][0] = 8;
5 ~/ V/ i- P( e- d3 Y
$p_arr[2][1] = $pfz_h/sqrt($pfm_h);
1 a, M/ {1 g+ c* K
}
) C: s! M) k8 m8 l
print_r($p_arr);
. ]* e3 l& O9 A7 ?
if($p_arr[0][1]>3){
( Y3 X9 A) ^/ X) b/ v0 W
echo "推荐h";
) |+ R1 R/ q" y$ R5 I) W2 P Z
}
- x2 i8 _ H2 w+ i
" u# ^2 V# h' n0 F
$p_arr是对Leo的推荐数组,其内容类似如下;
4 ~) S# k. J- j2 Z7 b0 q* M. U6 ~
. K7 O& Q) S/ N. `# H: S
Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )
* y, p' H Q# P* f) j6 V
8 L3 p4 B$ C+ z) c
f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........
^* g0 c$ ?* N$ F5 z _9 ~0 \
: P* S( S# v) ~2 M1 u$ r6 g8 n
求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。
, F# K0 N7 q' x2 j/ i! H
! H! K x7 P" {7 y: }' b
从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
y& ? K9 `- \1 K
( I( [0 ?: u0 e6 j- Y, j
1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
u5 q8 P3 O1 G: t* `
7 w8 c( U; v1 d% G# ]# U; a
2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。
% ~$ P1 v2 s( f' N1 h
6 O( W v7 B' d* \ ^) n" p1 t
3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
$ W$ M5 a9 j3 [& _7 I) w
) i. S: h9 H5 E, }+ F
4.可以适当引进基于内容的推荐,来完善推荐算法。
, l9 E! N9 M4 K( ]- f7 f5 Z; J
; ^% M* j, { w, W$ N$ R) U
5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。
1 k9 c \, {. m+ H
————————————————
; V& z: I$ H/ ^. ?
版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
/ |. H4 _7 M+ G* ^
原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
" {, P# n3 R2 e$ q
6 H4 c! u: z) x( O; Z
* ~) x+ |. j9 r* M
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5