一个基于SSM框架与物品的协同过滤算法(ItemCF)的简单电子书推荐系统
因部分推荐算法需要使用用户的喜爱数据作为参数。若用户未登录就采用对游客的 推荐策略。若用户已登录就采用对登录用户的推荐策略。其中若登录用户在数据库中存 在感兴趣的分区记录的话就会增加一个来自你感兴趣的分区的推荐。所以将推荐策略分为是否登录两种情况进行区别。
若用户未登录就采用对游客的用户评分显示策略。若用户已登录就采用对登录用户 的用户评分显示策略。其中若登录用户已经对当前详情页的电子书进行过评分,则显示 其评分记录。
如用例图所示,本系统中的基本用户分为 3 种。分别是游客、注册用户、管理员。游客可以访问电子书推荐平台的首页、用户注册页面、查看电子书页面。注册用户比游 客多的功能在于可以对电子书进行评分与评论和由该用户预测兴趣度决定的电子书推 荐。而管理员则可以定期使用爬虫模块来更新电子书信息和使用统计模块更新分类统计 信息、电子书同现矩阵和电子书余弦相似度矩阵。
![User Case.png](/img/User Case.png)
考虑到该算法存在冷启动问题。即对于新用户往往缺少评分数据从而导致根据用户预测兴趣度来进行推荐不能顺利进行。为解决该问题,我增加了使用余弦相似度矩阵 w 来直接进行相似电子书推荐的模块,方便未注册用户与新用户更好的获得推荐。而对于协同过滤算法存在的数据矩阵稀疏问题,即可能存在部分冷门电子书没有用户评分、电子书关联度不高、部分用户对电子书评分少等情况。为解决该问题,在首页上增加新书 推荐模块来更好的推荐没有人评分的电子书;在首页上增加根据用户选择感兴趣的分区 推荐模块以增加对具体的用户的兴趣来推荐电子书的拟合度。值得注意的是,用户预测兴趣度推荐的电子书是在该算法所计算出来的余弦相似度矩阵 W 上使用公式计算而来。并且在计算的时候还需要用户喜爱的电子书作为输入参数。 而相似电子书推荐则是直接使用弦相似度矩阵 W 进行统计。这就意味着以上两个推荐需 要先完成电子书同现矩阵和电子书余弦相似度矩阵的计算才可以进行。因为以上两个矩阵计算量较大且每次计算都需要使用全部数据进行计算,所以在此设置成由管理员定期来执行。
符号 → 表示该类为定期手动运行模块
基于物品的协同过滤算法主要有两步:
设 N(i)是表示喜欢物品 i 的用户数。N(i)⋂N(j)表示同时喜欢物品 i 物品 j 的用户数。则物品 i 与物品 j 的相似度为:
但是上式有一个缺陷:当物品 j 是一个很热门的商品时,人人都喜欢,那么 wij就会很接近于 1,即上式会让很多物品都和热门商品有一个很大的相似度,所以可以改进一下公式:
建立用户物品倒排表(设用大写字母表示用户,小写字母表示物品):
计算共现矩阵 C(共现矩阵 C 表示同时喜欢两个物品的用户数,是根据用户物品倒排表计算出来的):
如图可知共现矩阵的对角线元素全为 0,且是实对称稀疏矩阵。 算法实现如下:
com.statistics.ItemCollaborationFilter
/**
* 计算共现矩阵C
*/
private void computerMatrixC(){
// 建立用户物品倒排表
// 若用户对物品评分大于等于4则认为喜欢(出现)
List<User> allUser = userDao.queryAllUser();
for(int i = 0; i < allUser.size(); i++){ // 遍历全部用户
// 获取一个用户的评分列表中>=4的评分记录
List<RatingList> likeList = ratingListDao.selectRatingListByUidAndRatingValue(allUser.get(i).getUid(), 4);
if(likeList.size() <= 1){ // 若用户只喜欢一本或不喜欢任何图书
continue;
}
for(int j = 0; j < likeList.size(); j++){ // 计算likeList中两两出现的图书并写入同现矩阵C
for(int k = j+1; k < likeList.size(); k++){
int a = Integer.valueOf(likeList.get(j).getEid());
int b = Integer.valueOf(likeList.get(k).getEid());
// 生成key
String key = null;
if(a < b){
key = a + "," + b;
}else{
key = b + "," + a;
}
// 检查key是否已经存在
if(this.matrixC.get(key) != null){
int value = this.matrixC.get(key);
this.matrixC.put(key, value+1);
}else{
this.matrixC.put(key, 1);
}
}
}
System.out.println("["+df.format(new Date())+"]"+"[已完成"+i+",共"+allUser.size()+"]:用户uid="+allUser.get(i).getUid()+"的记录以计算完成,共"+likeList.size()+"本图书");
}
}
统计可得每个物品出现的次数为:
计算余弦相似度矩阵 W:使用改进后的公式计算可得余弦相似度矩阵。
算法实现如下:
com.statistics.ItemCollaborationFilter
/**
* 计算余弦相似度矩阵W
* 计算方法:
* 使用矩阵C的每个value作为分子,key中的两个图书的喜欢人数的积开根号作为分母
*/
private Double computerMatrixW(String eida, String eidb, int value){
DecimalFormat df = new DecimalFormat("#.##");
// 查询每个图书有多少人喜欢
try {
Statement statemenet = conn.createStatement();
ResultSet rs = statemenet.executeQuery("select count(rid) from ratinglist where eid = '"+ eida +"' and ratingValue >= 4;");
rs.next();
int likeANum = rs.getInt("count(rid)");
rs = statemenet.executeQuery("select count(rid) from ratinglist where eid = '"+ eidb +"' and ratingValue >= 4;");
rs.next();
int likeBNum = rs.getInt("count(rid)");
if(likeANum == 0)
likeANum = 1;
if(likeBNum == 0)
likeBNum = 1;
// 开始计算
Double answer = value*1.0/Math.sqrt(likeANum*likeBNum);
// 精确到小数点后两位
Double result = Double.parseDouble(df.format(answer));
// 返回计算结果
return result;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
最终推荐的是什么物品,是由预测兴趣度决定的。
物品j预测兴趣度 = 用户喜欢的物品i的兴趣度 × 物品i和物品j的相似度。
例如:某个用户喜欢物品 a、b 和 c。对其兴趣度分别为 1、2、2。那么物品 c、d、e、f 的预测兴趣度分别为:
所以应当向该用户推荐物品 d。 算法实现如下:
@Override
public List<EBook> userRecommendedList(String uid) {
// 获取用户喜爱图书列表
List<RatingList> likeList = this.ratingListDao.selectRatingListByUidAndRatingValue(uid, 4);
// debug
System.out.println("uid="+uid+"用户喜爱图书列表");
for(RatingList r : likeList){
System.out.println(r.getEid()+","+r.getRatingValue());
}
System.out.println("likeList.size="+likeList.size());
// 定义计算用矩阵
List<Item> matrix = new ArrayList<>();
// 将用户喜爱的图书作为矩阵的列
// 将与用户喜爱的图书同现的图书作为矩阵的行
// 建立工作矩阵
for(int i = 0; i < likeList.size(); i++){ // 遍历用户喜爱的图书
RatingList temp = likeList.get(i);
// 获取同现图书
List<MatrixC> itemList = this.matrixCDao.selectMatrixCByEidAOrEidB(temp.getEid(), temp.getEid());
for(int j = 0; j < itemList.size(); j++){
MatrixC c = itemList.get(j);
// 从matrixC的key中选出同现图书的eid
String sEid = null;
if(c.getEida().equals(temp.getEid())){
sEid = c.getEidb();
}else{
sEid = c.getEida();
}
// 在行中查询同现图书是否存在
if(matrix.indexOf(sEid) == -1){ // 若列中不存在
double[] col = new double[likeList.size()];
// 将同现图书所在行对应喜爱图书的数组值设为对应的余弦相似度*用户喜爱程度(4分为1,5分为2)
col[likeList.indexOf(temp)] = c.getCos_similarity()*(temp.getRatingValue()-3);
matrix.add(new Item(sEid, col)); // 增加行
}else{ // 若列中存在
// 则将同现图书所在行对应喜爱图书的数组值设为对应的余弦相似度*用户喜爱程度(4分为1,5分为2)
matrix.get(matrix.indexOf(sEid)).col[likeList.indexOf(temp)] = c.getCos_similarity()*(temp.getRatingValue()-3);
}
}
}
// 计算预测兴趣度
for(int i = 0; i < matrix.size(); i++){
Item item = matrix.get(i);
double interestValue = 0;
for(int j =0; j < item.col.length; j++){
interestValue += item.col[j];
}
matrix.get(i).interestValue = interestValue;
}
// 根据预测兴趣度进行排序
Collections.sort(matrix);
// 返回推荐图书列表
List<EBook> resultList = new ArrayList<>();
for(int i = 0; i < matrix.size() && i < 20; i++){ // 返回排前10的书
if(matrix.get(i).interestValue > 0){
EBook eBook = this.eBookDao.queryEBookByEid(matrix.get(i).eid);
resultList.add(eBook);
// debug
System.out.println(matrix.get(i).eid+","+eBook.getEname()+",interestValue="+matrix.get(i).interestValue);
}
}
return EBookServiceImpl.initEBookImgAddress(resultList);
}
推荐模块使用爬虫爬取的数据作为输入,将计算结果输出到 matrixC 表中。整个计算过程分为 2 个阶段来进行。第一阶段计算出共现矩阵 C。第二阶段计算出两两出现的电子书的余弦相似度 w。而对于根据用户预测兴趣度来推荐这一功能因为用户喜爱电子书数据的实时性和总计算量太大,则采用用户在访问页面时实时进行计算。经过多次测试,用户平均等待时间在可以接受的范围。
因为豆瓣电影网页升级加入反爬措施,所以在此提供用于运行推荐算法的数据
我们知道,linux下的man手册是我们开发很好的参考资料,为了快速的定位出我们要参考资料,我们可以在查询的时候指定资料的section索引号。man在内部把按照功能将这些资料分为了9个seciton,这些section索引号与对应section功能的对应关系如下: section number description 1 Executable programs or shell commands
man命令是linux下查找shell命令、函数等使用方法的利器。最简单的使用方式是man <the thing you want>。掌握上面那条命令应该也可以满足80%的使用场景了。这里记录一些更加深入的man命令使用的方法,如果还不能满足查询需求,就只能man man再深挖了。 man <section> <pagename> 这条命令可以在指定的section中去查询想要搜索的指南,因为一个
一、man是什么? man所代表的的是英文单词manual,也就是帮助手册的意思,Linux中的man手册就是提供给用户在有不明白的命令或者函数的时候,去查询它的功能、使用方法、头文件以及所需参数的帮助手册。 二、man命令的使用 1.通过man man查看man手册 man man命令可以查看man手册的内容(部分如下): MAN(1)
Linux man命令的使用方法(整理收集自网络) Linux提供了丰富的帮助手册,使用Linux man命令来查看一些不熟悉的命令的使用方法,还可以用来查询系统库文件中的一些函数定义和使用方法。 Linux man中的man就是manual的缩写,用来查看系统中自带的各种参考手册,但是手册页分为好几个部分,如下所示: 1 Executable programs or shell comman
“在Windows下,按下F1即可获取详尽的软件帮助页面,同样,在Linux下,每个条命令也可以通过man命令来获取使用手册。” 在Linux下,man命令的使用方法为:man + [要查询的命令] 例如,查询man命令的使用手册可以在Terminal中输入以下指令 man man 运行后将会得到以下页面 MAN(1)
linux下的man命令是一个帮助命令,掌握这个命令可以让我们更好的学习linux,下面由学习啦小编为大家整理了linux中的man命令的详细解释的相关知识,希望对大家有帮助! 一、linux中的man命令的详细解释 man命令是Linux下的帮助指令,通过man指令可以查看Linux中的指令帮助、配置文件帮助和编程帮助等信息。 语法 man(选项)(参数) 选项 -a:在所有的man帮助手册中搜
一. 前言 Linux man命令对于方便查看Linux的命令和系统函数的用法起到了很大的作用,可以说是Linux使用者的一把利器。但是,对于初学者甚至接触linux较久的人,对于man的用法还只是停留在"man xxx函数"的阶段,设置某些函数还不知道怎么通过man命令查到。 下面我列举几个man命令使用的痛点: 1. 有些命令的手册通过man命令只能查到基础部分
Mac 10.13 安装中文版 man 命令 本文参考于 《Mac 安装man命令中文文档》,但原文提供的链接以及安装的版本比较老旧。因此重新整理新版在这边提供给大家。 为什么需要 man 以及 man 怎么使用 linux 或者 mac 系统的命令行工具非常多,可是我们不能记住所有的这些命令,通常只能记住一些我们常用的。遇到不常用的我们需要来查询一下这个命令是怎么使用的。这时候我们就需要使用到
Linux 安装中文 man 手册章节介绍 Linux 的man手册共有以下几个章节: Linux提供了丰富的帮助手册,当你需要查看某个命令的参数时不必到处上网查找,只要man一下即可。 Linux 的man手册共有以下几个章节: 章节 名称 描述 1 Standard commands(标准命令) Executable programs or shell commands, 普通的命令 2 Sy
作用:查看Linux中指令的帮助、配置文件帮助、编程函数的帮助等信息。 语法:man [OPTION...] [SECTION] PAGE man命令的所有参数如下: [root@lena file]# man --help Usage: man [OPTION...] [SECTION] PAGE... -C, --config-file=FILE use this user co