PHP + MySQL 协同过滤推荐
最近毕设项目中需要用到推荐,不得不把校内实习的推荐算法欠账补一补。
参考文章:动手实现基于协同过滤的电影推荐系统
话不多说上代码:
建表
用户历史记录:
用户身份、歌单的点击次数、歌单ID
CREATE TABLE `themusic`.`history` (
`UserEmail` VARCHAR(80) NOT NULL,
`countClick` int default 0,
`listClick` VARCHAR(80) not null,
CONSTRAINT history_ibfk_1
FOREIGN KEY (`UserEmail`) REFERENCES `users` (`UserEmail`)
ON DELETE CASCADE ON UPDATE CASCADE
);
用户推荐列表:
每个只做四个推荐:
CREATE TABLE `themusic`.`recommend` (
`UserEmail` VARCHAR(80) NOT NULL,
`recommentList_1` VARCHAR(80) not null,
`recommentList_2` VARCHAR(80) not null,
`recommentList_3` VARCHAR(80) not null,
`recommentList_4` VARCHAR(80) not null,
CONSTRAINT recommend_ibfk_1
FOREIGN KEY (`UserEmail`) REFERENCES `users` (`UserEmail`) ON DELETE CASCADE ON UPDATE CASCADE
);
推荐
读取用户
从用户表中读取所有用户信息,用户Email做数组键名。
$arr_to_cos = array() ; //用户的二维数组
$arr_user = array(); //存储余弦相似度
$userListNum = array(); //用户点击的歌单总数
$sql = "select users.UserEmail from users ";
$result = mysqli_query($sqli_con,$sql);
if(mysqli_num_rows($result)>0){
while($row = mysqli_fetch_assoc($result)){
$arr_to_cos[$row['UserEmail']] = array();
$arr_user[$row['UserEmail']] = array();
$userListNum[$row['UserEmail']] = array();
}
}
读取信息
$sql = "select * from history ";
$result = mysqli_query($sqli_con,$sql);
if(mysqli_num_rows($result)>0){
while($row = mysqli_fetch_assoc($result)){
$arr_to_cos[$row['UserEmail']][$row['listClick']] = $row['countClick'];
}
}
foreach($arr_to_cos as $Email => $user) { //注意$v前面的&,引用指针
$userListNum[$Email] = count($user);
}
其中:
$arr_to_cos[$row['UserEmail']][$row['listClick']] = $row['countClick'];
从历史记录中读取用户点击的歌单ID 和 次数,UserEmail中歌单ID做键名,点击次数做键值。
foreach($arr_to_cos as $Email => $user) { //注意$v前面的&,引用指针
$userListNum[$Email] = count($user);
}
遍历二维数组中的每一个用户,计算用户单击歌单总数存入 $userListNum
进行推荐
foreach($arr_to_cos as $uEmail => $user)//遍历每一个用户
foreach($arr_to_cos as $oEmail =>$other){ //每一个用户相对其他用户
if($uEmail == $oEmail)continue;
/*array_intersect_key 函数,比价两个数组键值,返回交集*/
/*$user 和$other 分别存储用户A和B 点击过的歌单数据(ID=>Count)*/
/*$union表示用户A和B 共同点击过的歌单数据*/
$union = array_intersect_key($user,$other);
/*$num表示用户A和B 共同点击过的歌单个数*/
$num = count($union);
/*计算两个用户之间的余弦相似度*/
if($userListNum[$uEmail] * $userListNum[$oEmail] != 0){
$arr_user[$uEmail][$oEmail] = $num /( sqrt($userListNum[$uEmail] * $userListNum[$oEmail]) );
}else{
$arr_user[$uEmail][$oEmail] = 0;
}
echo $num;
}
余弦相似度:
相对用户A与其他用户 的余弦相似度排序:
arsort($arr_user[$uEmail]);
从中选取相似度最高的 K(3)个用户:
$max_K = array_slice($arr_user[$uEmail],0,3);
初始化一个数组,存储K个用户的所有歌单:
$max_K_list = array();
对歌单打分,用户对歌单的感兴趣程度表现为点击歌单的次数
foreach($max_K as $oEmail =>$cos){ //K个用户 及其余弦相似度
foreach ($arr_to_cos[$oEmail] as $list => $count){ //每个用户的点击数据
/*如果 $max_K_list 中存在 $list */
if(array_key_exists($list,$max_K_list) ){
/*$count 点击次数,表示用户感兴趣程度 */
$max_K_list[$list] += $count * $cos;//更新数据
}else{
$max_K_list[$list] = $count * $cos;//添加数据
}
}
}
计算用户u 对 与u最相似的K个用户点击过的歌单 的感兴趣程度(打分):
对K个用户点击过得所有歌单 按照 上一步计算的分数 排序:
arsort($max_K_list);
取分数最高的 N (4) 个歌单 ID:
$recommend = array_slice($max_K_list,0,4,true);
此时歌单ID是键名,把它提取出来:
$relist = array();
foreach ($recommend as $li => $vi){
array_push($relist,$li);
}
接下开查找歌单,存入推荐列表:
$sql = "select * from recommend where UserEmail = '".$uEmail."'";
$result = mysqli_query($sqli_con,$sql);
if(mysqli_num_rows($result)>0){
$sql = "update recommend set recommentList_1 ='".$relist[0].
"',recommentList_2 = '".$relist[1].
"',recommentList_3 = '".$relist[2].
"',recommentList_4 = '".$relist[3].
"'where UserEmail = '".$uEmail."'";
$result = mysqli_query($sqli_con,$sql);
}else{
$sql = "insert into recommend VALUES ('".$uEmail."','".$relist[0].
"','".$relist[1]."','".$relist[2]."','".$relist[3]."')";
$result = mysqli_query($sqli_con,$sql);
}
结束
2020/06/08 更新
使用过程中发现,算法会向用户推荐历史记录中的歌曲,在用户量小时尤为明显,因为用户本身历史记录包含在M个用户浏览的N个歌单中。
计算用户感兴趣程度后,与历史记录取差集可避免推荐列表中出现历史记录。
$max_K_list = array_diff_key($max_K_list,$user);