目录
前言
内容推荐在很多公司实践都很常见,由于本博主不会人工zz,所以使用别人的算法mahout算法,其中的基于用户协同算法推荐。
数据库准备
链接:https://pan.baidu.com/s/1vPwGq_gu2i9Y1jf4CV8AIQ
提取码:jbw3
pom.xml
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-core</artifactId>
<version>0.9</version>
</dependency>
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-integration</artifactId>
<version>0.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
这里使用jpa疯狂造假数据,还有导入mahout包
配置
#通用数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?charset=utf8mb4&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
# Hikari 数据源专用配置
spring.datasource.hikari.maximum-pool-size=100
spring.datasource.hikari.minimum-idle=20
# JPA 相关配置
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=update
mahout配置
import com.mysql.cj.jdbc.MysqlDataSource;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.model.jdbc.MySQLJDBCDataModel;
import org.apache.mahout.cf.taste.model.DataModel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
@Configuration
public class MahoutConfig {
private MysqlDataSource getDataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setServerName("127.0.0.1");
dataSource.setUser("root");
dataSource.setPassword("xxx");
dataSource.setDatabaseName("test");
try {
dataSource.setServerTimezone("UTC");
} catch (SQLException e) {
e.printStackTrace();
}
return dataSource;
}
@Bean(autowire = Autowire.BY_NAME, value = "mySQLDataModel")
public DataModel getMySQLJDBCDataModel() {
DataModel dataModel = new MySQLJDBCDataModel(getDataSource(), "test", "uid", "iid",
"score", "ts");
return dataModel;
}
@Bean(autowire = Autowire.BY_NAME, value = "fileDataModel")
public DataModel getDataModel() throws IOException {
URL url = MahoutConfig.class.getClassLoader().getResource("mahout/ratings-1m.data");
//URL url = MahoutConfig.class.getClassLoader().getResource("mahout/dajitui.data");
DataModel dataModel = new FileDataModel(new File(url.getFile()));
return dataModel;
}
}
mySQLDataModel代表数据库的源,fileDataModel代表文件源,在resource下
MySQLJDBCDataModel里面参数,数据库,数据表名,用户id,喜欢item id,评分,时间
获取预测值
URL url = MahoutConfig.class.getClassLoader().getResource("mahout/dajitui.data");
DataModel dataModel = new FileDataModel(new File(url.getFile()));
// 相似度计算(皮尔森相似度)
UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
// 设置相似用户阈值(或使用NearestNUserNeighborhood)
UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.1, similarity, dataModel);
// 基于以上数据创建推荐器(这里使用的是基于用户的推荐,还有GenericItemBasedRecommender等推荐器)
Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
本项目的思路
为啥到这里才说推荐项目的思路呢?因为我们可以看到上面有两种数据源,文件、数据库。
你们可以试一下,使用数据库会很慢,而且经常说连接数不足,为啥?你一个连接分析7秒多,要不就超过数据库的超时时间,要不就占用连接,导致其他线程在wait for lock
所以我才有将所有数据库的数据存储到文件中,进行分析。
数据库数据存储到文件中
@GetMapping("/insert")
public long doSomething() throws IOException {
long time = System.nanoTime();
File file = new File(MahoutConfig.class.getClassLoader().getResource("mahout").getPath() + "/dajitui.data");
if (!file.exists()) {
file.createNewFile();
}
List<TestDO> testDOList = Optional.ofNullable(testDao.findAll()).orElse(new ArrayList<>());
FileOutputStream fileOutputStream = new FileOutputStream(file, true);
for (TestDO testDO : testDOList) {
String msg = testDO.getUid() + "," + testDO.getIid() + "," + testDO.getScore() + "\n";
fileOutputStream.write(msg.getBytes());
}
fileOutputStream.close();
long time1 = System.nanoTime();
return time1 - time;
}
5201760370纳秒(43.6万数据生成data文件耗费5秒多,6.54 MB)
在target文件夹下class包生成
预测某用户喜欢的item id
@GetMapping("/mahout")
public List getSomething(int uid) throws TasteException, IOException {
long time = System.nanoTime();
URL url = MahoutConfig.class.getClassLoader().getResource("mahout/dajitui.data");
DataModel dataModel = new FileDataModel(new File(url.getFile()));
// 相似度计算(皮尔森相似度)
UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
// 设置相似用户阈值(或使用NearestNUserNeighborhood)
UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.1, similarity, dataModel);
// 基于以上数据创建推荐器(这里使用的是基于用户的推荐,还有GenericItemBasedRecommender等推荐器)
Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
List<RecommendedItem> recommendItems = recommender.recommend(uid, 5);
if (!CollectionUtils.isEmpty(recommendItems)) {
for (RecommendedItem recommendedItem : recommendItems) {
ResultDO resultDO = new ResultDO();
resultDO.setUid(5);
resultDO.setArticleid(recommendedItem.getItemID());
resultDO.setValue(new BigDecimal((recommendedItem.getValue())));
resultDao.save(resultDO);
}
long time1 = System.nanoTime();
System.out.println(time1 - time);
return recommendItems;
}
long time1 = System.nanoTime();
System.out.println(time1 - time);
return new ArrayList();
}
分析一个用户推荐文章耗费496321161纳秒(0.49秒)
统计所有预测结果到数据库
@GetMapping("/tongji")
public long didSomething() throws IOException, TasteException {
long time = System.nanoTime();
URL url = MahoutConfig.class.getClassLoader().getResource("mahout/dajitui.data");
DataModel dataModel = new FileDataModel(new File(url.getFile()));
// 相似度计算(皮尔森相似度)
UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
// 设置相似用户阈值(或使用NearestNUserNeighborhood)
UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.1, similarity, dataModel);
for (int i = 0; i <= 435995; i += 5) {
// 基于以上数据创建推荐器(这里使用的是基于用户的推荐,还有GenericItemBasedRecommender等推荐器)
Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
List<RecommendedItem> recommendItems = null;
try {
recommendItems = recommender.recommend(i, 5);
} catch (Exception e) {
log.error("查询出错:{}", e.getMessage());
}
if (!CollectionUtils.isEmpty(recommendItems)) {
List<ResultDO> resultDOList = new LinkedList<>();
for (RecommendedItem recommendedItem : recommendItems) {
ResultDO resultDO = new ResultDO();
resultDO.setUid(i);
resultDO.setArticleid(recommendedItem.getItemID());
resultDO.setValue(new BigDecimal((recommendedItem.getValue())));
resultDOList.add(resultDO);
}
if (!CollectionUtils.isEmpty(resultDOList)) {
resultDao.saveAll(resultDOList);
}
}
}
long time1 = System.nanoTime();
return time1 - time;
}
在这里卡到怀疑人生
分析
统计分析结果其实是不怎么现实,一个0.49秒,如果你有10w粉丝,统计下来就是13小时多了。
那开多线程去跑呢?10w粉丝,由于是cpu密集型,线程数跟cpu核心数一样4个,也得跑3个多小时。
总结
除非是有另一台空闲机器去跑,不然对线上服务器压力蛮大的。预想:单独用户id去查询结果缓存起来,用户阅读之后删除或者下次更新推荐的文章。
github
如果对你有帮助记得点个星星start哦,谢谢老铁
反思
我们可以看到才有文件的方式,数据库方式也一样,数据量不能那么high大的,不然对储存都有压力。这个是缺点也很好解决,可以删掉之前的数据呀。不过如果对于大公司来说,推荐这个东西会一直变,所以这里可能会使用人工智障去进行机器学习获得数据模型等等。
其次是在统计上存在困难,毕竟统计基数在哪里,什么东西成上去就瞬间变大。举个栗子:每个中国人给我一块钱,那么我就有13亿,可能还不止。so如果统计1w粉丝,10w粉丝基数在那里,耗费时间也会很长的。个人目前思路:多线程跑,然后不在项目的服务器跑,而是放到另一台服务器跑,避免影响业务运作,这时候分布式系统的优势就体现出来了。不过不是所有的公司都这么有钱,233.