Libimseti推荐系统,Libimseti系统
Libimseti推荐系统,Libimseti系统
技术:easyUI、jQuery、Spring、Struts、Hibernate、Mahout、MySQL
本Libimseti推荐系统使用数据、代码参考《Mahout in action》第五章内容。
系统可以从这里下载:libimesti推荐系统。
1. 系统部署
1.1 数据库
(1)修改Configuration目录中的db.properties中的数据库配置;(2)从http://www.occamslab.com/petricek/data/libimseticomplete.zip下载所需要的数据,解压后可以看到gender.dat 和ratings.dat文件;(3)启动工程,自动生成相关表;(4)在数据库中运行sql目录下sql,导入相关数据;1.2 公共配置
(1)修改src目录下com.fz.util.Utils中的genderFile和ratingsFile变量为正确文件地址;2. 系统功能
2.1 Libimseti推荐
启动tomcat,访问http://localhost:8080/rec 即可访问系统主页,如下: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中的”添加“按钮):这里使用的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 目录添加
点击添加按钮,可以对目录进行添加,其界面如下:
其中,图标使用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
其实最流行的XP其实有以下几个版本:Windows XP Professional(专业版)、Windows XP Home(家庭版)、Windows XP SP1(专业版)、Windows XP SP2。
另外还有一种GHOST版的XP,是特别制作的,有很多版本。
个人还是觉得非GHOST版的Windows XP sp1原始安装光盘最好用!要免激活的,如俄罗斯破解版。sp2对某些程序有兼容性问题,建议不要用什么上海政府版的GHOST版本克隆安装,这样虽然比较快,但也有很多弊病。
这种安装盘用GHOST的方法将XP系统连同一些常用的软件一并克隆到你的硬盘上,外面有很多卖的,网上有下载的,不过很大,差不多一张CD的容量,五、六百兆左右,下载要很长时间呢,而且下了后,你还要用软件加刻录机把它做成光盘才能用,这种光盘都有一个共同的特点,能启动电脑,前提是你在CMOS中设置了电脑首先从光驱启动。
不过不推荐你用这种光盘,为什么呢?因为它虽然是非常方便,但是弊端也很大,系统是经过修改的,而且集成的软件不管你是否需要,都一股脑的给你装上,而且往往很多软件版本已落后。这种盘虽然可以适应几乎所有的机器,但是一些功能可就要大打折扣了,你想想看,每个人的电脑都不一样的,它要保证每台都能用,能完美的实现吗?不能吧,所以,还是自己一步一步安装XP,再装好适合自己用的软件,最后用GHOST备份,这才是最明智的做法。
当然XP的克隆盘并不是没有用的,用它当启动光盘就非常不错,虽然软盘也能做启动盘,但很容易坏,好好利用启动光盘里的GHOST会给你带来不少便利,而且一般这种光盘的启动菜单里还有不少很实用的工具,不过不熟悉的情况下可别盲目使用。
我就非常喜欢用这种光盘,手头有好几张呢,恢复系统都靠它,处理问题、进DOS也靠它,它是我离不开的工具,但我从来不装里面的克隆版XP。
建议直接安装w7吧,xp明年起,微软就不提供补丁了,w7需要的内存也不大,2g内存完美运行w7.我的笔记本就是。
评论暂时关闭