数学建模社区-数学中国
标题:
php+mysql实现简单的协同过滤推荐算法
[打印本页]
作者:
杨利霞
时间:
2022-9-8 10:21
标题:
php+mysql实现简单的协同过滤推荐算法
/ @8 P, b% S: L. R1 |2 x
php+mysql实现简单的协同过滤推荐算法
; e* i6 b1 F+ m/ U/ f: P
仅做标记。。。
0 k% |- p& h; N+ o$ [
; P7 k$ n' ]+ F* E2 J- {* G9 w8 B
: d, z8 ]9 O/ F6 \+ ]
+ J; h+ ]% ?% n/ o, g; [% E, A
要实现协同过滤推荐算法,首先就要理解算法的核心思想和流程。该算法的核心思想可以概括为:若a,b喜欢同一系列的物品(暂时称b是a的邻居吧),则a很可能喜欢b喜欢的其他物品。算法的实现流程可以简单概括为:1.确定a有哪些邻居 2.通过邻居来预测a可能会喜欢哪种物品 3.将a可能喜欢的物品推荐给a。
2 F" z; [2 V5 U8 i9 p) e
1 E. }6 P7 Z5 w4 A1 o" m
算法核心的公式如下:
( Z7 c# Z( d2 B3 `
9 Q- X& z5 d; s/ b4 [
1.余弦相似度(求邻居):
, y) X! Z' v6 x* f9 X! M; X
5 d; f2 A9 ~8 H% j$ ~7 \
2.预测公式(预测a可能会喜欢哪种物品):
2 f t4 M; L' ^, g) Q: S# `+ p4 L
- b8 |8 e% s+ J% C
仅从这两个公式我们就可以看出,仅仅是按照这两个公式进行计算,就需要进行大量的循环与判断,而且还涉及到排序的问题,就涉及到排序算法的选择与使用,这里我选快排,从网上copy了一段快排,直接用。总之实现起来很麻烦,在大数据情况下,更何谈效率。
) Q4 S3 W5 ]2 L
$ M# z6 J5 H1 b( _
首先建表:
, h' |: _: J/ G: ~- q, {4 `' L
/ @0 `' ~' V( d
DROP TABLE IF EXISTS `tb_xttj`;
& S& o2 |* \5 A% [
CREATE TABLE `tb_xttj` (
; K# {& n G* r+ Y: X" o/ ~7 |: O; M
`name` varchar(255) NOT NULL,
9 l- [9 h; S$ P# N$ o; V4 `; u. |
`a` int(255) default NULL,
/ ?, b/ y( a* z/ s8 Q9 W
`b` int(255) default NULL,
( a m& G+ k+ [, T! X
`c` int(255) default NULL,
9 a& }* g( G3 ?2 I0 r4 b
`d` int(255) default NULL,
3 \* q6 v; \+ j) P0 I) E& n5 X
`e` int(255) default NULL,
- i4 q7 q5 j+ q
`f` int(255) default NULL,
+ n! ]5 R; \0 Q
`g` int(255) default NULL,
1 j" j# D3 z2 Z7 V
`h` int(255) default NULL,
2 f4 A2 b3 K: U. R; J% c2 O& O
PRIMARY KEY (`name`)
8 Z% ~; ^' u( t p9 {/ T0 }! }
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
" f/ j! g" [& F* Y B# _
7 l8 w1 q% s+ F1 m( B
INSERT INTO `tb_xttj` VALUES ('John', '4', '4', '5', '4', '3', '2', '1', null);
+ @5 w* ^1 j- y7 I
INSERT INTO `tb_xttj` VALUES ('Mary', '3', '4', '4', '2', '5', '4', '3', null);
% W; I+ B3 s$ I$ ]
INSERT INTO `tb_xttj` VALUES ('Lucy', '2', '3', null, '3', null, '3', '4', '5');
}" y4 w" p$ B, u0 n- E- v
INSERT INTO `tb_xttj` VALUES ('Tom', '3', '4', '5', null, '1', '3', '5', '4');
. Y3 D; j V, P+ m, M; d; u# n
INSERT INTO `tb_xttj` VALUES ('Bill', '3', '2', '1', '5', '3', '2', '1', '1');
$ @* p0 v2 k1 e: N5 o) m' _) O
INSERT INTO `tb_xttj` VALUES ('Leo', '3', '4', '5', '2', '4', null, null, null);
8 {5 ?4 A1 j- p& A8 C8 W
4 K* U! U2 S. a" o0 h) ~
1 D3 @( F8 E# k/ h
我这里只对最后一行的Leo进行推荐,看看f,g,h哪个可以推荐给他。
" _0 T: p' d* D& Y1 s! M4 V
( s* t0 {6 ^9 {9 S4 A& _0 Q. {
用php+mysql,流程图如下:
1 z- e# i: G1 `: k
3 m) |7 S1 l# J6 z+ P$ q* [: p
连接数据库并将其存储为二维数组的代码如下:
6 T9 X0 B1 w- [( ]% ?& u& ^7 v
( [* c' _8 A0 _$ J7 l
header("Content-Type:text/html;charset=utf-8");
( @/ v! S. g; D5 p' A$ @
$ m( a5 e5 a9 s6 V6 q. y# v' m& q
mysql_connect("localhost","root","admin");
7 J4 j2 n5 m) L' w; W5 k" h7 a4 l
mysql_select_db("geodatabase");
/ K* M, v; E3 y8 k
mysql_query("set names 'utf8'");
5 q2 B; h0 |0 Z! l0 s
. C5 z3 T) u& l ^9 p
$sql = "SELECT * FROM tb_xttj";
+ U6 w2 Y j- X) Z7 h0 f
$result = mysql_query($sql);
( Q: P8 m7 O0 y! T
* u2 {3 B' G9 ?3 ^: j( f
$array = array();
2 B' n: h: _; x3 n; X* h, ?6 U5 a
while($row=mysql_fetch_array($result))
9 ]+ X/ { r1 [3 ^; m6 p, \; y
{
+ `* N& P+ W, h; b1 G. ?% l
$array[]=$row;//$array[][]是一个二维数组
( q% X& V' y4 a( G5 K: J( }
}
" P1 E& v8 Q k, W9 o5 V6 ~
9 C9 f# a2 X3 @- v" A
问题1:这一步完全可以看做是整表查询,这种查询是大忌,对于这种小小的演示系统还可以,但是对大数据的系统,没有效率,至于如何改进,还得多学习才是。
! l% x: b" }* p7 g. i
0 s9 X& j/ ^. v, m: F( Y
求Leo与其他人的Cos值代码如下:
0 r" |0 l M+ Q4 [; T+ u
F! ]: m; x' p/ b- n7 H( e& ?
/*
; f: g6 X W4 E& N
* 以下示例只求Leo的推荐,如此给变量命名我也是醉了;初次理解算法,先不考虑效率和逻辑的问题,主要把过程做出来
" C, k" ]0 w3 c& G4 H- ^# M0 Q
*/
8 }# z2 B. C7 ?4 U6 t) Y
2 I6 E7 E% M. S7 v. \: A% I
$cos = array();
( Z7 t# X( W/ p' ?. X
$cos[0] = 0;
; j" y# t: P# f- v
$fm1 = 0;
- y/ K7 U- z1 B! q
//开始计算cos
; d/ V' A6 x3 P$ p
//计算分母1,分母1是第一个公式里面 “*”号左边的内容,分母二是右边的内容
, w1 K2 G- J# U8 N: `5 b
for($i=1;$i<9;$i++){
. \+ B/ N# A! Y0 D( I v& }
if($array[5][$i] != null){//$array[5]代表Leo
1 e) h4 k i d+ ^- p3 s! z9 Q
$fm1 += $array[5][$i] * $array[5][$i];
" G- v' G( R0 @ x: b1 G& T
}
a2 B5 u! ?+ t$ v; [/ l
}
) K k9 _4 V: ^0 \2 }9 G) N
( q" @4 j" c, [
$fm1 = sqrt($fm1);
4 A. v# P4 v$ }
. d# \. \5 F9 }
for($i=0;$i<5;$i++){
$ A! Z: D& E( }
$fz = 0;
$ S6 x9 z3 B1 _, h( [ K
$fm2 = 0;
5 X* p0 M1 v O; ]
echo "Cos(".$array[5][0].",".$array[$i][0].")=";
" w/ M" z9 Z9 \. J8 s; b+ O0 S
' b6 O, q$ L! h7 x7 x
for($j=1;$j<9;$j++){
. N! w7 }9 Q! B* h$ U( z4 }5 {" N! z
//计算分子
6 w& e& H+ I/ \' Q# v8 D
if($array[5][$j] != null && $array[$i][$j] != null){
" C- d/ }- K0 Y& q
$fz += $array[5][$j] * $array[$i][$j];
2 s4 I7 J9 I" j7 ^8 j Z9 f5 }& f8 D/ v
}
- ^2 h! H2 S, O+ N
//计算分母2
/ E; v. Y3 V5 W! r
if($array[$i][$j] != null){
& S+ M$ g* B/ s" j; F
$fm2 += $array[$i][$j] * $array[$i][$j];
: Q6 I3 _' R- S1 Q
}
( H4 @) O/ Z, `" s+ G" C) N$ A' M
}
) {. q9 ?, a# g
$fm2 = sqrt($fm2);
) `+ H4 d, w* @: k) s
$cos[$i] = $fz/$fm1/$fm2;
1 s8 I4 M2 L! N9 B# N2 c( Z
echo $cos[$i]."<br/>";
) ^4 w, c( v6 E/ ^4 n+ `9 o/ u6 B
}
8 J; I* |! b; f: q
# f' d1 z8 C$ L
这一步得到的结果是酱紫:
, f8 Y- n9 C/ B
. e, f- Y5 @/ p. K
将求好的Cos值排序,采用快排代码如下(百度copy而来):
2 l. A% R( d, g5 z
) l" ?& N5 m! [4 ^( t
* Q, L" {0 U0 ^* f! P' f
//对计算结果进行排序,凑合用快排吧先
6 G, q# x/ f, Y
function quicksort($str){
; s- p2 l/ Q; g8 B* O9 i
if(count($str)<=1) return $str;//如果个数不大于一,直接返回
4 W/ a; l r& J% B- c$ S
$key=$str[0];//取一个值,稍后用来比较;
: Y, N$ W5 [! b0 `- }3 {2 j
$left_arr=array();
9 n+ |( H" A: G7 l
$right_arr=array();
% J/ l: X, q+ F& ]* o# r
: E6 a3 W8 P* S
for($i=1;$i<count($str);$i++){//比$key大的放在右边,小的放在左边;
" O1 i5 \, W$ [) z* Q" }
if($str[$i]>=$key)
" n% l5 X- m; z6 ]8 G0 C
$left_arr[]=$str[$i];
# ^% h* g% X6 ]1 j! J4 M
else
6 l: M& W, z, |7 s' c
$right_arr[]=$str[$i];
) L4 ^( m, F/ f6 Z% n6 X( j" l/ P
}
1 I E' [: H! X
$left_arr=quicksort($left_arr);//进行递归;
1 o4 ^3 p3 b- h
$right_arr=quicksort($right_arr);
. n2 L$ v6 S. g2 g k/ h
return array_merge($left_arr,array($key),$right_arr);//将左中右的值合并成一个数组;
, l& t8 p3 r& N7 D: h
}
+ }1 C- ?' n. f2 p
6 H. _- Q( k, s7 q4 v+ N, Y, r; E
$neighbour = array();//$neighbour只是对cos值进行排序并存储
) ]! u! P7 n1 b
$neighbour = quicksort($cos);
4 J. f% z) E* L( M' Y
( G0 B7 E: w, D9 T/ b$ o! r) @
" k+ Y. b& I6 B
这里的$neighbour数组仅仅存储了从大到小排序好的Cos值,并没有与人联系起来。这个问题还要解决。
. j L6 x4 x9 M! D$ H
, z4 H, g1 ~: g5 k( C8 `
选出Cos值最高的3个人,作为Leo的邻居:
' I8 m& w: g* s3 U2 C
) W- Y9 r7 L8 |
//$neighbour_set 存储最近邻的人和cos值
+ d: N ?* k, S- V
$neighbour_set = array();
. @! m( L/ G2 w
for($i=0;$i<3;$i++){
# U# t2 d1 K4 h4 h9 a! B# J
for($j=0;$j<5;$j++){
9 l8 _- w# J4 d4 H/ X- m9 ?
if($neighbour[$i] == $cos[$j]){
8 f6 u. p, V+ J) @1 G7 k
$neighbour_set[$i][0] = $j;
! h& x' R4 r3 o# P
$neighbour_set[$i][1] = $cos[$j];
8 C1 Y2 `) \, }& G b' \/ E
$neighbour_set[$i][2] = $array[$j][6];//邻居对f的评分
% ~8 M: m* m s
$neighbour_set[$i][3] = $array[$j][7];//邻居对g的评分
C8 e! G0 E8 r6 @5 H
$neighbour_set[$i][4] = $array[$j][8];//邻居对h的评分
' L3 V2 z7 Z" v: m# y* M
}
( n9 S" d8 K8 D7 ?. R
}
, N& I2 J+ |" K7 h' \( | b
}
: V1 v. R7 |) [+ E3 N* w8 J& b
print_r($neighbour_set);
; a8 ]% x8 E9 X4 \& h+ V, v
echo "<p><br/>";
6 R! i$ X1 [9 V0 _6 X
7 U+ M; z/ ?% b* O6 I9 j* C
这一步得到的结果是酱紫:
8 l* k8 K }5 n5 o. F
% x n$ T$ R$ w, Q" D
2 \! w( v& ~/ S) ^8 a6 K
7 ~# V# Z/ U1 O* y$ S( s
转存失败重新上传取消
: a: v% E: f9 K5 C# K
5 ~) ~& h8 _9 j: X4 k/ ^
这是一个二维数组,数组第一层的下标为0,1,2,代表3个人。第二层下标0代表邻居在数据表中的顺序,比如Jhon是表中的第0个人;下标1代表Leo和邻居的Cos值;下标2,3,4分别代表邻居对f,g,h的评分。
' k- w4 W2 }! z
! E6 g8 L9 K! K) z' W4 | e
开始进行预测,计算Predict代码如下:
$ E. ?/ A6 A" }; Q. H& Q
4 O" k0 i; r2 N- [3 t( A8 W0 [3 D% M
我是分别计算Leo对f,g,h的预测值。在此有一个问题,就是如果有的邻居对f,g,h的评分为空,那么该如何处理。比如Jhon和Mary对h的评分就为空。本能的想到用if判断一下,如果为空则跳过这组计算,不过这样处理是否合理,有待考虑。以下代码并没有写出这个if判断。
+ L2 n! X5 o- v% u) f4 o
0 O# t @6 ~- f9 f
//计算Leo对f的评分
& Y: d# G) s' v9 q
$p_arr = array();
* c4 c3 w( R) d/ Y
$pfz_f = 0;
4 `! A& t6 f" \7 D; i* w* ?6 ]
$pfm_f = 0;
1 o* M; G1 K! o6 {0 j q: n
for($i=0;$i<3;$i++){
0 |2 K2 w+ d5 I/ \/ L
$pfz_f += $neighbour_set[$i][1] * $neighbour_set[$i][2];
& U9 Y( w( X( U
$pfm_f += $neighbour_set[$i][1];
, e5 Z3 x' @- W- s- O0 @" s, B
}
4 y6 |! {- I" p6 ?! r; w0 \6 E; ^
$p_arr[0][0] = 6;
: k, T' s/ R7 d4 d' Y; k
$p_arr[0][1] = $pfz_f/sqrt($pfm_f);
2 G# z1 c* S1 a! r ?5 M
if($p_arr[0][1]>3){
2 r' q: e& J4 A0 T: i
echo "推荐f";
5 i _$ W& F0 Q' e$ L/ P
}
) w4 m9 |# X+ v ~! ?& O( i
1 |8 w+ s- E$ M) b1 E
//计算Leo对g的评分
9 a6 ]: `& b- G- R" _8 U$ g( O
$pfz_g = 0;
: j9 `2 O& X( F2 f' G& h. x
$pfm_g = 0;
0 o: q$ M" N: E
for($i=0;$i<3;$i++){
/ x- [; M- b1 N, b% _
$pfz_g += $neighbour_set[$i][1] * $neighbour_set[$i][3];
3 g7 D% m% Q5 l+ T7 q4 |6 G
$pfm_g += $neighbour_set[$i][1];
7 d- N8 z: ]+ `) E
$p_arr[1][0] = 7;
8 l( V$ i R+ t/ b/ h8 [6 K0 R
$p_arr[1][1] = $pfz_g/sqrt($pfm_g);
6 i: Z. g6 {- o9 A; T* O2 T$ Z
}
# U" y K$ R) ?
if($p_arr[0][1]>3){
3 m9 q: E1 X5 r# j! d" w% b Y% h
echo "推荐g";
5 X& f" k: F- U4 M6 |
}
1 e4 X) I* }7 w; \5 {
h5 Z' I J8 C+ B4 S- Z
//计算Leo对h的评分
( o. L+ b' Z" |. j% x6 U; e+ K; F) K
$pfz_h = 0;
: X" e- I! O! m8 N* A
$pfm_h = 0;
1 k/ R _7 g0 s2 e
for($i=0;$i<3;$i++){
' ]8 A( A; R% t+ D) H$ g
$pfz_h += $neighbour_set[$i][1] * $neighbour_set[$i][4];
, ~" \0 r3 n+ u/ ?7 @3 V) i8 U
$pfm_h += $neighbour_set[$i][1];
: `$ d; ]3 y- h- O3 F
$p_arr[2][0] = 8;
5 l1 G- u F1 |6 } H+ ^# W
$p_arr[2][1] = $pfz_h/sqrt($pfm_h);
d3 q; y$ ]$ v
}
: i1 n) y3 E4 F- _& u. ^# @
print_r($p_arr);
% T4 N) z$ p) F3 Q) q
if($p_arr[0][1]>3){
" Y1 A C4 T+ Z$ L( F, d E' O
echo "推荐h";
7 j+ m: U! o- J8 _4 t0 o3 l
}
; _* U. a* v; ~9 H! V
3 Z8 T* X/ t& n3 G' G. W
$p_arr是对Leo的推荐数组,其内容类似如下;
- p# `* C9 Z# C6 i2 V: t; A& J i% i
- x0 Y0 p8 a- ^ q
Array ( [0] => Array ( [0] => 6 [1] => 4.2314002228795 ) [1] => Array ( [0] => 7 [1] => 2.6511380196197 ) [2] => Array ( [0] => 8 [1] => 0.45287424581774 ) )
& P# Y9 T7 m% ?6 O
* b1 n7 K2 \ K+ Q8 f! x0 D
f是第6列,Predict值是4.23,g是第七列,Predict值是2.65........
3 v+ W+ ]* c3 G: S, [3 @( ]
2 G( J8 o7 R+ e: e$ m1 q
求完了f,g,h的Predict值后有两种处理方式:一种是将Predict值大于3的物品推荐给Leo,另一种是将Predict值从大到小排序,将Predict值大的前2个物品推荐给Leo。这段代码没有写。
5 y$ M y! i- Z5 S) P) i/ J
# t, ^$ \" l: \, m
从上面的示例中可以看出,推荐算法的实现非常麻烦,需要循环,判断,合并数组等等。如果处理不当,反而会成为系统的累赘。在实际处理中还有以下问题:
5 W, D2 H, c1 t0 B/ i& O
- L. q% ~* Q2 K, Y0 u: u5 |; U% p1 o
1.以上示例我们只对Leo进行推荐,而且我们已经知道Leo没有评价过f,g,h物品。如果放到实际的系统里,对于每一个需要进行推荐的用户,都要查询出他没有评价过哪些物品,这又是一部分开销。
9 ?; ~2 a3 J. j4 h% I# w% i7 x
: Z! ~, v) b {( `
2.不应当进行整表查询,在实际系统中可以设定一些标准值。比如:我们求Leo与表中的其他人的Cos值,如果该值大于0.80,则表示可以为邻居。这样,当我找到10个邻居之后,就停止求Cos值,避免整表查询。对于推荐物品也可以适当采用此方法,比如,我只推荐10个物品,推荐完后就停止求Predict值。
; L D0 q+ ~. v L& r/ h8 Y- M2 H# h
5 e" h! T; a) }0 g5 g
3.随着系统的使用,物品也会发生变化,今天是fgh,明天没准就是xyz了,当物品变化时,需要动态的改变数据表。
& o3 L# D; Z9 C; ?
' G! r1 z6 ~0 L* K
4.可以适当引进基于内容的推荐,来完善推荐算法。
1 V9 E- {- u, C! i/ x; r. P
/ N& P7 P/ `! O2 q/ V' w
5.推荐的精确性问题,这个设置不同的标准值,会影响精确性。
% _( q" k P6 ~0 [# W5 e* P
————————————————
) ^! r: c% l6 N1 }1 d) p
版权声明:本文为CSDN博主「星斗其文,赤子其人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
P+ a ~1 k2 k% [
原文链接:https://blog.csdn.net/liuliuhelingdao/article/details/126715465
. M# ^9 j4 D4 O/ G
: V; n6 U* w2 B2 W" t( j
9 j7 Q/ e% m4 y3 P: `
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5