技术:easyUI、jQuery、Spring、Struts、Hibernate、Mahout、MySQL

本Libimseti推荐系统使用数据、代码參考《Mahout in action》第五章内容。

系统能够从这里下载:libimesti推荐系统 或  http://pan.baidu.com/s/1nvzqWcx (包括源代码)。

1. 系统部署

1.1 数据库

(1)改动Configuration文件夹中的db.properties中的数据库配置;
(2)从http://pan.baidu.com/s/1bOzCtC 下载所须要的数据,解压后能够看到gender.dat 和ratings.dat文件;
(3)启动project,自己主动生成相关表。
(4)在数据库中执行src文件夹下*.sql。导入相关数据;

1.2 公共配置

(1)改动src文件夹下com.fz.util.Utils中的genderFile和ratingsFile变量为正确文件地址。

2. 系统功能

2.1 Libimseti推荐

启动tomcat,訪问http://localhost:8080/rec 就可以訪问系统主页。例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZmFuc3kxOTkw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="700" height="350" alt="" />

2.1.1 用户评分档案查询

在推荐算法页面点击”查询”按钮。就可以依据用户ID输入框里面的用户ID查询用户对其它档案的评分,同一时候这里把用户的性别和档案的性别一起查出来了。

这里显示使用的是easyUI的datagrid。其后台代码例如以下:
$('#ratingId').datagrid({
border:false,
// fitColumns:true,
singleSelect:true,
// width:600,
height:200,
nowrap:false,
// fit:true,
pagination:true,//分页控件
pageSize:4, // 每页记录数,须要和pageList保持倍数关系
pageList:[4,8,12],
rownumbers:true,//行号
// pagePosition:'bottom',
url:'rating/rating_getRatingData.action',
queryParams: {
uid: userIdValue,
selectgender:selectGender
},
toolbar: "#toolbar",
columns:[[
{field:'id',title:'用户ID',width:'50'},
{field:'gender',title:'用户Gender',width:'80'},
{field:'itemId',title:'档案ID',width:'120'},
{field:'pref',title:'档案评分',width:'150'},
{field:'itemGender',title:'档案Gender',width:'100'},
{field:'desc',title:'档案描写叙述',width:'200',},
]]
});

使用了toolbar。提供“加入”、“改动”和“删除“功能,使用分页组件用于分页显示查询数据。因为用户评分数据和用户性别数据是在两个表中,所以新建了一个中间类UserRating用于组装数据传入前台,代码例如以下:

public Map<String,Object> getRatingByUId(Integer uId,int rows,int page, char selectgender){
String hql = "from Rating r where UID="+uId +" order by r.uId,r.itemId";
String hqlCount ="select count(1) from Rating where UID="+uId;
String hqlGender = "from Gender where UID="+uId;
List<Rating> ratings = baseDAO.find(hql,new Object[]{},page,rows);
List<UserRating> userRatings = new ArrayList<UserRating>(); if(ratings.size()<=0){
return null;
} // 获取用户Gender
// List<Gender> gender =genderDAO.find(hqlGender);
char uGender = genderDAO.find(hqlGender).get(0).getGender();
char itemGender;
UserRating ur = null;
for(Rating rating:ratings){
ur= new UserRating();
ur.setId(uId);
ur.setDesc(rating.getDesc());
ur.setItemId(rating.getItemId());
ur.setPref(rating.getPref());
ur.setGender(uGender);
// 获取ITEM gender
hqlGender="from Gender g where UID="+rating.getItemId();
itemGender =genderDAO.find(hqlGender).get(0).getGender();
ur.setItemGender(itemGender);
userRatings.add(ur);
}
Map<String ,Object> jsonMap = new HashMap<String,Object>();
jsonMap.put("total", baseDAO.count(hqlCount));
jsonMap.put("rows", userRatings);
return jsonMap;
}

这里的selectgender变量,本来是在页面加入的一个用于在查询时过滤性别的变量,后面感觉有点麻烦就没做了(性别数据在gender表。分页针对的是rating表);

2.1.2 用户加入对其它未评分档案数据

用户加入对其它未评分档案数据的页面例如以下(点击toolbar中的”加入“按钮):

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZmFuc3kxOTkw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

这里使用的easyUI的window组件,打开页面后依据用户的信息先初始化用户ID和用户性别两个性别,且不可改动。用户须要输入档案ID(必选项)、档案性别和档案描写叙述;
用户输入档案ID的时候,使用ajax实时向后台发送消息。查询用户是否对档案ID已经评分过。假设评分过就进行如图的提示,此功能首先对validatebox进行扩展。然后使用Validator的框架进行验证。代码例如以下:
// 用户在添加对其它项目评分的时候,须要检查是否该项目用户已经评过分
$.extend($.fn.validatebox.defaults.rules, {
hasItem : {
validator : function(value,param) {
var uid = $('#uidId').val();
console.info("value:"+value+",user:"+uid); return hasItem(uid,value);
},
message : '用户已对该项目评过分!'
}
}); // 检查用户是否对项目评过分
function hasItem(user,item){
if(isNaN(parseInt(item))){
return false;
}
var boolean =false;
$.ajax({ // 获取数据
url : "rating/rating_hasItem.action",
data : "uid=" + user+"&itemid="+item,
dataType : "json",
async:false,
success : function(data) {
console.info("用户"+user+"是否对项目"+item+"评分? "+data);
// 设置
if(data==false||data=="false"){
boolean=true;
}
}
});
return boolean;
}

这样在jsp页面就能够简单的使用以下的代码就可以:

<input class="easyui-validatebox" type="text" name="itemid" id="itemidId" style="width:100px"
data-options="required:true,
validType:'hasItem'"/>

2.1.3 用户改动当前档案信息

改动用户当前档案信息界面例如以下:
当中的用户ID和档案ID是不可改动的;这里弹出的window和加入功能界面的window是一样的,这里在弹出界面的时候改动其title。

2.1.4 删除用户对当前档案数据

删除用户对当前档案数据须要用户进一步确认:

2.1.5 非过滤推荐

在tomcat启动过程中会对推荐系统进行初始化,这样在推荐的时候直接能够使用推荐模型进行推荐,这样推荐的时候就不用等待过多时间;
默认使用过滤推荐,非过滤推荐即不使用用户的gender数据对最后的推荐数据进行过滤。
jquery获取是否过滤推荐的checkbox的状态:
$('#filterId').click(function() {

	    if(this.checked){
filter=true;
}else{
filter=false;
}
console.info("filter:"+filter);
});

推荐相同使用easyUI的datagrid,其js例如以下:

$('#recommendId').datagrid({
border:false,
singleSelect:true,
height:180,
nowrap:false,
pagination:true,//分页控件
pageSize:4, // 每页记录数,须要和pageList保持倍数关系
pageList:[4,8,12],
rownumbers:true,//行号
url:'rec/rec_getRecommendData.action',
queryParams: {
uid: userIdValue,
filter:filter
},
columns:[[
{field:'uid',title:'用户ID',width:'50'},
{field:'ugender',title:'用户Gender',width:'80'},
{field:'itemid',title:'档案ID',width:'120'},
{field:'pref',title:'档案评分',width:'150'},
{field:'itemgender',title:'档案Gender',width:'100'},
]] });

这里传入后台的參数中包括filter和uid。filter即是否使用过滤;

2.1.6 过滤推荐

首先,这里使用Mahout的基于用户的协同过滤算法进行推荐(非MR方式)。
其次。这里的过滤规则例如以下:首先计算出用户评价过的档案中的性别的较大值,比方M(men)(即对哪类性别的档案评分比較多),然后在对用户进行推荐的可能档案中不正确非M的进行计算。直接去掉。这样在最后推荐的时候就不会出现非M性别的档案了。

推荐使用Mahout的基于用户的协同过滤算法,同一时候在《Mahout in action》中对这个代码进行了包装,代码例如以下:
package com.fz.service;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import javax.annotation.Resource; import org.apache.mahout.cf.taste.common.Refreshable;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastIDSet;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.IDRescorer;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.stereotype.Service; import com.fz.dao.BaseDAO;
import com.fz.model.Gender;
import com.fz.model.RecommendRating;
import com.fz.util.GenderRescorer;
import com.fz.util.Utils; /**
* libimseti 推荐
* 使用《Mahout in action 》第五章代码
* 使用MySQL数据库作为数据源,则算法非常慢
* @author fansy
*
*/
@Service("recommend")
public class LibimsetiRecommender implements Recommender {
private Recommender delegate;
private DataModel model;
private FastIDSet men;
private FastIDSet women;
private FastIDSet usersRateMoreMen;
private FastIDSet usersRateLessMen;
@Resource
private BaseDAO<Gender> genderDAO; private boolean filter=true;
/**
* 从数据库中获取DataModel
* @return
* @throws IOException
* @throws TasteException
* @throws NumberFormatException
*/ public LibimsetiRecommender() throws NumberFormatException, TasteException, IOException{
this(localDataModel());
}
private static DataModel localDataModel() throws IOException { FileDataModel dataModel = new FileDataModel(new File(Utils.ratingsFile));
return dataModel;
} public LibimsetiRecommender(DataModel model) throws TasteException, NumberFormatException, IOException{ UserSimilarity similarity = new EuclideanDistanceSimilarity(model);
UserNeighborhood neighborhood =
new NearestNUserNeighborhood(4,similarity,model);// 增大n值能够获得很多其它推荐
delegate =
new GenericUserBasedRecommender(model,neighborhood,similarity);
this.model=model;
FastIDSet[] menWomen = GenderRescorer.parseMenWomen(new File(Utils.genderFile));
men = menWomen[0];
women = menWomen[1];
usersRateMoreMen = new FastIDSet(50000);
usersRateLessMen = new FastIDSet(50000);
} @Override
public void refresh(Collection<Refreshable> alreadyRefreshed) {
delegate.refresh(alreadyRefreshed);
} @Override
public List<RecommendedItem> recommend(long userID, int howMany)
throws TasteException {
IDRescorer rescorer= null;
if(filter){
rescorer=new GenderRescorer(men,women,usersRateMoreMen,usersRateLessMen,userID,model);
}
return delegate.recommend(userID, howMany, rescorer);
} /**
* 推荐整合
* @throws TasteException
*/
public Map<String, Object> recommend(long userID,int rows,int page,boolean filter) throws TasteException{
this.filter=filter;
String gHql = "from Gender g where g.uId=?";
List<RecommendedItem> recommend = recommend(userID,20);
Map<String ,Object> jsonMap = new HashMap<String,Object>();
List<RecommendRating> tmp = new ArrayList<RecommendRating>();
RecommendRating rating =null;
if(recommend.size()<=0){
rating = new RecommendRating();
rating.setUid(-1);
rating.setUgender('U');
rating.setItemid(-1);
rating.setItemgender('U');
rating.setPref(-1);
tmp.add(rating);
jsonMap.put("total", recommend.size());
jsonMap.put("rows", tmp);
return jsonMap;
}
List<RecommendRating> recommendRatings = new ArrayList<RecommendRating>();
char uGender = genderDAO.get(gHql, new Object[]{(int)userID}).getGender(); for(RecommendedItem re:recommend){
rating = new RecommendRating();
rating.setUid(userID);
rating.setUgender(uGender);
rating.setItemid(re.getItemID());
rating.setItemgender(genderDAO.get(gHql, new Object[]{(int)re.getItemID()}).getGender());
rating.setPref(re.getValue());
recommendRatings.add(rating);
} for(int i=(page-1)*rows;i<page*rows&&i<recommend.size();i++){
tmp.add(recommendRatings.get(i));
}
jsonMap.put("total", recommend.size());
jsonMap.put("rows", tmp);
return jsonMap;
} @Override
public List<RecommendedItem> recommend(long userID, int howMany,
boolean includeKnownItems) throws TasteException {
return delegate.recommend(userID, howMany, includeKnownItems);
} @Override
public List<RecommendedItem> recommend(long userID, int howMany,
IDRescorer rescorer) throws TasteException {
return delegate.recommend(userID, howMany, rescorer);
} @Override
public List<RecommendedItem> recommend(long userID, int howMany,
IDRescorer rescorer, boolean includeKnownItems)
throws TasteException {
return delegate.recommend(userID, howMany, rescorer, includeKnownItems);
} @Override
public float estimatePreference(long userID, long itemID)
throws TasteException {
IDRescorer rescorer= new GenderRescorer(men,women,
usersRateMoreMen,usersRateLessMen,userID,model);
return (float)rescorer.rescore(userID, itemID);
} @Override
public void setPreference(long userID, long itemID, float value)
throws TasteException {
delegate.setPreference(userID, itemID, value);
} @Override
public void removePreference(long userID, long itemID)
throws TasteException { delegate.removePreference(userID, itemID);
} @Override
public DataModel getDataModel() {
return delegate.getDataModel();
} public Recommender getDelegate() {
return delegate;
} public void setDelegate(Recommender delegate) {
this.delegate = delegate;
} public DataModel getModel() {
return model;
} public void setModel(DataModel model) {
this.model = model;
} public FastIDSet getMen() {
return men;
} public void setMen(FastIDSet men) {
this.men = men;
} public FastIDSet getWomen() {
return women;
} public void setWomen(FastIDSet women) {
this.women = women;
} public FastIDSet getUsersRateMoreMen() {
return usersRateMoreMen;
} public void setUsersRateMoreMen(FastIDSet usersRateMoreMen) {
this.usersRateMoreMen = usersRateMoreMen;
} public FastIDSet getUsersRateLessMen() {
return usersRateLessMen;
} public void setUsersRateLessMen(FastIDSet usersRateLessMen) {
this.usersRateLessMen = usersRateLessMen;
}
public boolean isFilter() {
return filter;
}
public void setFilter(boolean filter) {
this.filter = filter;
} }

代码分析:

1. 初始化时首先会调用localDataModel方法。这种方法用于初始化数据模型。我曾试过使用mysqlDataSource做为数据源,可是计算太慢了。

2. 带參数的LibimsetiRecommender构造方法,就是主要的推荐代码了创建UserSimilarity、UserNeighborhood对象,这里的n值(代码中为4)能够依据自己的须要进行调整,原书中为2。同一时候在这个构造方法中还对gender数据进行了读取,把数据放入内存,方便依据用户ID查询性别。
3. 推荐使用recommend(int userid ,int howmany)就可以,这里代码使用的howmany固定为20。同一时候因为数据须要传入前台。同一时候考虑到分页。所以写了一个recommend(long userID,int rows,int page,boolean filter)方法。用于进行数据分页处理。
4. 在recommend(int userid,int howmany)中假设使用了过滤,那么就初始化IDRescorer为GenderRescorer,当中GenderRescorer为自己定义过滤器,这里须要注意代码清单 Listing5.4 Gender-based rescoring Implementation中的代码有一个地方有问题。原版为:
	public boolean isFiltered(long id) {
// TODO Auto-generated method stub
return filterMen? men.contains(id):women.contains(id);
}

须要改为:

	public boolean isFiltered(long id) {
// TODO Auto-generated method stub
return filterMen? (!men.contains(id)):(!women.contains(id));
}

isFiltered方法其解释为 true to exclude, false otherwise,这个解释和代码是不一样的。

2.1.7 过滤推荐和非过滤推荐对照

比方针对用户ID为8的用户,其过滤推荐为:
这里事实上现实的是没有推荐。再看非过滤推荐:
这里能够看到有3个推荐,可是假设对用户ID为8的用户使用非过滤推荐,那么能够看到这个用户可能是GAY,可是从用户8的评分数据来看。其对F(Female)的档案评分比較多。这说明这3个推荐是不合理的。须要过滤,那么过滤推荐就能够过滤掉这三个推荐数据了。

2.1.8 匿名推荐

待更新。

2.2 文件夹维护

2.2.1 文件夹改动

点击导航配置Tab,能够看到文件夹维护的界面:
这里的操作里面的按钮,使用以下的方式生成:
$(function() {
$('#catalogId')
.datagrid(
{
border : false,
fitColumns : true,
singleSelect : true,
width : 600,
height : 250,
nowrap : false,
fit : true,
pagination : true,// 分页控件
pageSize : 4, // 每页记录数,须要和pageList保持倍数关系
pageList : [ 4, 8, 12 ],
rownumbers : true,// 行号
pagePosition : 'top',
url : 'catalog/catalog_getTreeData.action',
columns : [ [
{
field : 'id',
title : '节点ID',
width : '40'
},
{
field : 'text',
title : '节点名称',
width : '120'
},
{
field : 'url',
title : 'URL',
width : '150'
},
{
field : 'pid',
title : '父节点ID',
width : '60'
},
{
field : 'iconCls',
title : '图标',
width : '100'
},
{
field : 'opt',
title : '操作',
width : '40',
formatter : function(value, row, index) { var btn_edit = '<button type="button" onclick="update('
+ row.id + ')">编辑</button>';
var btn_remove = '<button type="button" onclick="deleteRow('
+ row.id + ')">删除</button>';
return btn_edit + btn_remove;
}
} ] ] }); });

2.2.1 文件夹加入

点击加入按钮,能够对文件夹进行加入,其界面例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZmFuc3kxOTkw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="300" height="260" alt="" />

当中。图标使用combobox,其图标加入代码例如以下:

$('#iconId').combobox(
{
formatter : function(row) {
var imageFile = 'themes/icons/' + row.icon;
console.info('imageFile' + imageFile);
return '<img class="item-img" src="' + imageFile
+ '"/>  <span class="item-text">'
+ row.text + '</span>';
}
});

分享,成长。快乐

转载请注明blog地址:http://blog.csdn.net/fansy1990

Libimseti推荐系统的更多相关文章

  1. SVD的几何意义,以及在去噪,推荐系统中的应用

    很多文章说到奇异值分解的时候总是大概罗列下它的功能,并没有对功能及物理意义进行过多的阐述,现在我来对奇异值进行整理一下. 一 奇异值分解 对任意的矩阵A∈Fmn,rank(A)=r(矩阵的秩),总可以 ...

  2. F#之旅6 - 简单AV推荐系统

    上回说到用F#来写爬虫,这只是F#学习第一阶段的第一步.最开始,就对第一阶段做了这样的安排: 1.爬虫爬取AV数据 2.数据处理和挖掘 3.数据可视化(使用ECharts) 4.推荐系统 第一步很快就 ...

  3. [Recommendation System] 推荐系统之协同过滤(CF)算法详解和实现

    1 集体智慧和协同过滤 1.1 什么是集体智慧(社会计算)? 集体智慧 (Collective Intelligence) 并不是 Web2.0 时代特有的,只是在 Web2.0 时代,大家在 Web ...

  4. 推荐相关学习 & 典型算法、典型特征、典型推荐系统框架

    总的来说,信息爆炸,产生了信息过载.解决的方法主要有两类:检索和推荐.检索是主动的有目的的.意图明确,推荐是非主动的.意图不明确. 推荐方面最经典的,就是协同过滤推荐了.我博客这里有两篇,一篇偏理论, ...

  5. 推荐系统学习--cb+cf 初见

    对于推荐系统的推出有两个条件:1.信息过载 ,2用户没有明确的需求 推荐系统算法中常见的有基于内容推荐,协同过滤推荐,协同过滤还可以分为基于人的协同过滤,基于内容协同过滤:社会推荐等 如何理解这些推荐 ...

  6. Amazon的推荐系统

    本文引自http://blog.csdn.net/fwing/article/details/4942886 现在的推荐系统特别火啊.做得最好的应该是Amazon了. 上面是Amazon的图书推荐. ...

  7. Java程序员的日常——SpringMVC+Mybatis开发流程、推荐系统

    今天大部分时间都在写业务代码,然后算是从无到有的配置了下spring与mybatis的集成. SpringMVC+Mybatis Web开发流程 配置数据源 在applicationContext.x ...

  8. 转:netflix推荐系统竞赛

    原文链接:Netflix recommendations: beyond the 5 stars (Part 1), (Part 2) 原文作者:Xavier Amatriain and Justin ...

  9. 推荐系统(协同过滤,slope one)

    1.推荐系统中的算法: 协同过滤: 基于用户 user-cf 基于内容 item –cf slop one 关联规则 (Apriori 算法,啤酒与尿布) 2.slope one 算法 slope o ...

随机推荐

  1. maven学习(七)——使用maven构建java项目

    构建Jave项目 1.使用mvn archetype:generate命令,如下所示: mvn archetype:generate -DgroupId=com.mycompany.app -Dart ...

  2. 项目记事【StreamAPI】:使用 StreamAPI 简化对 Collection 的操作

    最近项目里有这么一段代码,我在做 code-review 的时候,觉得可以使用 Java8 StreamAPI 简化一下. 这里先看一下代码(不是源码,一些敏感信息被我用其他类替代了): privat ...

  3. 【bzoj1336/1337/2823】[Balkan2002]Alien最小圆覆盖 随机增量法

    题目描述 给出N个点,让你画一个最小的包含所有点的圆. 输入 先给出点的个数N,2<=N<=100000,再给出坐标Xi,Yi.(-10000.0<=xi,yi<=10000. ...

  4. 【bzoj2096】[Poi2010]Pilots 双指针法+STL-set

    题目描述 Tz又耍畸形了!!他要当飞行员,他拿到了一个飞行员测试难度序列,他设定了一个难度差的最大值,在序列中他想找到一个最长的子串,任意两个难度差不会超过他设定的最大值.耍畸形一个人是不行的,于是他 ...

  5. SCU 4438 Censor(哈希+模拟栈)

    Censor frog is now a editor to censor so-called sensitive words (敏感词). She has a long text \(p\). He ...

  6. 请大家注意这个网站www.haogongju.net

    乱转发我的文章,求职之路(拿到百度.美团.趋势科技.华为offer),不注明出处,我把原来的博客删除了,被转载的文章还在,www.haogongju.net,你侵犯版权!!!请你自动撤销!!!

  7. transform总结

    1. 用jquery的css方法获取transform得到的是矩阵matrix,不利于获取translate的值, 优先使用dom.style.webKitTransform进行transform的读 ...

  8. val

    val 题目描述 有一个值初始为0,接下来n次你可以令其在之前基础上+2或+1或-1.你需要保证,这个值在整个过程中达到的最大值减去达到的最小值不大于k,求方案数,模1,000,000,007. 输入 ...

  9. 论文笔记《Notes on convolutional neural networks》

    这是个06年的老文章了,但是很多地方还是值得看一看的. 一.概要 主要讲了CNN的Feedforward Pass和 Backpropagation Pass,关键是卷积层和polling层的BP推导 ...

  10. filesystem

    1 tmpfs 以下来源于维基百科: tmpfs是类Unix系统上暂存档存储空间的常见名称,通常以挂载文件系统方式实现,并将数据存储在易失性存储器而非永久存储设备中.和RAM disk的概念近似,但后 ...