基于 Apache Mahout 实现高效的协同过滤推荐电影
Apache Mahout 是 Apache Software Foundation (ASF) 旗下的一个开源项目,提供一些可扩展的机器学习领域经典算法的实现,旨在帮助开发人员更加方便快捷地创建智能应用程序,并且,在 Mahout 的最近版本中还加入了对 Apache Hadoop 的支持,使这些算法可以更高效的运行在云计算环境中。
Apache Mahout 中提供的一个协同过滤算法的高效实现,它是一个基于 Java 实现的可扩展的,高效的推荐引擎。
Mahout里面包含了系列数据挖掘算法,taste算法,分类,聚类,关联规则
demo电影影城项目就得用到Mahout的一些推荐算法,还有必要的搜索引擎,下面呢我就说一下我的电影推荐-Taste算法之Java应用(电影的推荐)
Preference
基于协同过滤的推荐引擎的输入是用户的历史偏好信息,在 Mahout 里它被建模为 Preference(接口),一个 Preference 就是一个简单的三元组 < 用户 ID, 物品 ID, 用户偏好 >,它的实现类是 GenericPreference,因为占用内存太大。因此不推荐使用,而它自身呢,,Mahout 在这方面做了一些优化,它创建了 PreferenceArray(接口类)保存一组用户偏好数据,为了优化性能,Mahout 给出了两个实现类,GenericUserPreferenceArray 和 GenericItemPreferenceArray。
下面呢是我的代码解释:
PreferenceArray prefsForUser3 = new GenericUserPreferenceArray(4);//4 这里可以是很大,但是呢别超内存就行了
prefsForUser3.setUserID(1, 3L);
prefsForUser3.setItemID(1, 101L);
prefsForUser3.setValue(1, 5.0f);
prefsForUser3.setItemID(2, 102L);
prefsForUser3.setValue(2, 4.5f);
// 这样呢就建立好了一个user-----------》 这个用户信息:<3,101,5.0> <3,102,4.5>ataModel**out 的推荐引擎实际接受的输入是 DataModel,它是对用户偏好数据的压缩表示,通过创建内存版 DataModel 的语句我们可以看出:
DataModel model = new GenericDataModel(FastByIDMap map)
DataModel 是用户喜好信息的抽象接口,它的具体实现支持从任意类型的数据源抽取用户喜好信息,具体实现包括内存版的 GenericDataModel,支持文件读取的 FileDataModel 和支持数据库读取的 JDBCDataModel
GenericDataModel代码:
FastByIDMap preferences = new FastByIDMap();
PreferenceArray prefsForUser1 = new GenericUserPreferenceArray(4);
PreferenceArray prefsForUser2 = new GenericUserPreferenceArray(4);
PreferenceArray prefsForUser3 = new GenericUserPreferenceArray(4);
prefsForUser2.setUserID(0, 1L);
prefsForUser2.setItemID(0, 101L);
prefsForUser2.setValue(0, 3.0f);
prefsForUser2.setItemID(1, 102L);
prefsForUser3.setValue(1, 4.5f);//用户id=1
prefsForUser3.setUserID(1, 3L);
prefsForUser3.setItemID(1, 101L);
prefsForUser3.setValue(1, 5.0f);
prefsForUser3.setItemID(2, 102L);
prefsForUser3.setValue(2, 4.5f);//用户id=3
prefsForUser1.setUserID(2, 2L);
prefsForUser1.setItemID(2, 101L);
prefsForUser1.setValue(2, 3.0f);
prefsForUser1.setItemID(3, 102L);
prefsForUser1.setValue(3, 4.5f);//用户id=2
preferences.put(1L, prefsForUser3);
System.out.println(preferences.get(1L));preferences.get(1L);
preferences.put(2L, prefsForUser1);
preferences.put(3L, prefsForUser2);
DataModel model = new GenericDataModel(preferences);
DataModel model = new GenericDataModel(preferences);
这个训练模型方法可以接受,也很方便,虽然说内存比直接从数据库得出要快得多,但也占用了不少宝贵的内存资源。但是对于数据量很大的项目来说就不行了
FileDataModel:
DataModel model =new FileDataModel(new File(“E:/work2/movies/src/main/java/text.txt”));//绝对路径
新建一个数据测试文件text.txt内容有三列,分别是用户ID,商品ID,兴趣打分。
用文件训练出的模型可以说相比内存版,剩下了内存,和数据库版省了时间,但是如果数据量一旦超过G,就没效率的概念了吧
MySQLJDBCDataModel:
mahout也给了一个数据库版的接口,Mahout 提供一个默认的 MySQL 的支持,它对用户偏好数据的存放有以下简单的要求:
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setServerName("my_user");
dataSource.setUser("my_password");
dataSource.setPassword("my_database_host");
JDBCDataModel dataModel = new MySQLJDBCDataModel(dataSource, "my_prefs_table",
"my_user_column", "my_item_column", "my_pref_value_column");
1、首先呢建立一个项目,导包,如果是web项目呢直接用maven。
2、建立自己中意的model;在这里我用SSm
FileDataMo@RequestMapping("/allmovies")
@RequestMapping("/allmovies")
public String allmovies(HttpSession session) throws Exception{
/**
* 这是taste算法
* 求出这个id的推荐itemid
*/
List taste = new TasteItemId().taste();
for (RecommendedItem recommendedItem : taste) {
System.out.println(recommendedItem.getItemID());//打印itemid
}
return "main";
}
/**
*算法区
**/
DataModel model =new FileDataModel(new File("E:/work2/movies/src/main/java/text.txt"));
UserSimilarity similarity =new PearsonCorrelationSimilarity(model); //根据模型创建相似度
UserNeighborhood neighborhood =new NearestNUserNeighborhood(2,similarity,model);
Recommender recommender= new GenericUserBasedRecommender(model,neighborhood,similarity);
List recommendations =recommender.recommend(1L, 2);//给用户1推荐两个itemid
/**
*实时从数据库获取list并写入文件
*/
@WebListener
public class TasteIndexListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void contextInitialized(ServletContextEvent e) {
ServletContext ctx = e.getServletContext();
WebApplicationContext wctx = WebApplicationContextUtils.getWebApplicationContext(ctx);
final MainFindMoiesSerivceImp mms = wctx.getBean(MainFindMoiesSerivceImp.class);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
List ta = mms.findTaste();
//进行字符串拼接
List list=new ArrayList();
for (Taste taste : ta) {
StringBuffer buf=new StringBuffer();
buf.append(taste.getUserId());
buf.append(",");
buf.append(taste.getMoveId());
buf.append(",");
buf.append(taste.getValue());
String string = buf.toString();
list.add(string);
}
new TasteIo(list);//返回的是一个只带有一个元素的内容list
}
}, 20000,50000);//刷新间隔和时间
}
/**
*文件流写入list ,不要写进对象,因为文件格式一定要规范
*/
package inverse.movies.utils;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
public class TasteIo {
/**
* @param list传入要写入文件内容的集合
*/
public TasteIo(List list) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(new File("E:/work2/movies/src/main/java/text.txt")));
for(String str : list)
{
// 写文件
bw.write(str, 0, str.length());
// 刷新流
bw.flush();
// 换行
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭文件流
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
后台输出的结果:
17/08/11 10:24:55 INFO file.FileDataModel: Creating FileDataModel for file E:\work2\movies\src\main\java\text.txt
17/08/11 10:24:55 INFO file.FileDataModel: Reading file info...
17/08/11 10:24:55 INFO file.FileDataModel: Read lines: 21
17/08/11 10:24:55 INFO model.GenericDataModel: Processed 5 users
104
106